road update

This commit is contained in:
33333-33333 2026-05-26 21:14:37 +09:00
commit a6e66f2838
9 changed files with 2174 additions and 258 deletions

View file

@ -703,7 +703,7 @@ function naturalGroupKey(unit) {
if (unit.classId <= 3) return `urban:${Math.round(unit.x / 10)}:${Math.round(unit.y / 10)}`;
if (unit.classId === 5) return `coast:${Math.round(unit.y / 8)}`;
if (unit.classId === 6) return `basin:${Math.round(unit.x / 12)}:${Math.round(unit.y / 12)}`;
if (unit.classId === 7) return `valley:${Math.round((unit.x + unit.y) / 12)}`;
if (unit.classId === 7) return `valley:${Math.round(unit.x / 11)}:${Math.round(unit.y / 11)}`;
if (unit.classId === 8 || unit.classId === 9) return `mountain:${Math.round(unit.x / 14)}:${Math.round(unit.y / 14)}`;
return `plain:${Math.round(unit.x / 14)}:${Math.round(unit.y / 14)}`;
}
@ -1030,6 +1030,7 @@ function buildSeededNaturalCompartments(prefectureMask, sea, elevation, slope, r
let compartments = buildUnitsFromAssignment(compartmentId, cellClass, prefectureMask, sea, fields);
rebuildLandscapeUnitAdjacency(compartmentId, compartments, naturalBarrierScore, prefectureMask, sea);
mergeTinyLandscapeUnits(compartmentId, compartments, 9);
mergeWeakArtificialLandscapeUnits(compartmentId, compartments, fields, prefectureMask, sea, Math.max(42, Math.round(landArea / Math.max(1, targetCount) * 2.15)), 5);
progress?.(`natural post-split units: ${compartments.filter((unit) => unit && unit.area > 0).length}`);
splitDisconnectedCompartments(compartmentId, compartments, prefectureMask, sea);
compartments = renumberCompartments(compartmentId, compartments, prefectureMask, sea);
@ -1067,6 +1068,7 @@ function buildSeededNaturalCompartments(prefectureMask, sea, elevation, slope, r
}
}
mergeWeakArtificialLandscapeUnits(compartmentId, compartments, fields, prefectureMask, sea, Math.max(48, Math.round(landArea / Math.max(1, targetCount) * 2.05)), 4);
splitDisconnectedCompartments(compartmentId, compartments, prefectureMask, sea);
compartments = renumberCompartments(compartmentId, compartments, prefectureMask, sea);
refreshAllCompartmentStats(compartments, fields);
@ -1137,10 +1139,11 @@ function rebuildLandscapeUnitAdjacency(unitId, units, targetScore, prefectureMas
const b = unitId[ni];
if (b < 0 || b === a || !units[b] || units[b].area === 0) continue;
const v = (targetScore[i] + targetScore[ni]) * 0.5;
const keyA = units[a].adjacent.get(b) || { count: 0, target: 0 };
keyA.count++; keyA.target += v; units[a].adjacent.set(b, keyA);
const keyB = units[b].adjacent.get(a) || { count: 0, target: 0 };
keyB.count++; keyB.target += v; units[b].adjacent.set(a, keyB);
const vertical = nx !== x ? 1 : 0;
const keyA = units[a].adjacent.get(b) || { count: 0, target: 0, vertical: 0, horizontal: 0 };
keyA.count++; keyA.target += v; if (vertical) keyA.vertical++; else keyA.horizontal++; units[a].adjacent.set(b, keyA);
const keyB = units[b].adjacent.get(a) || { count: 0, target: 0, vertical: 0, horizontal: 0 };
keyB.count++; keyB.target += v; if (vertical) keyB.vertical++; else keyB.horizontal++; units[b].adjacent.set(a, keyB);
}
}
}
@ -1172,6 +1175,65 @@ function mergeTinyLandscapeUnits(unitId, units, minArea = 10) {
}
}
function mergeUnitInto(unitId, units, fromId, toId, fields) {
const from = units[fromId];
const to = units[toId];
if (!from || !to || from.area === 0 || to.area === 0 || fromId === toId) return false;
for (const ci of from.cells) { unitId[ci] = toId; to.cells.push(ci); }
from.area = 0;
from.cells = [];
refreshCompartmentStats(to, fields.elevation, fields.slope, fields.ridgeField, fields.valleyField, fields.basinField, fields.coastalLowland, fields.plain, fields.agriculture, fields.populationDensity, fields.landuse);
return true;
}
function mergeWeakArtificialLandscapeUnits(unitId, units, fields, prefectureMask, sea, maxMergedArea = 84, passes = 5) {
// Seeded graph growth can create diagonal stair-step borders in uniform plains
// and gentle hills. If the shared edge is weak, balanced H/V, and the two
// sides are the same natural group, merge it instead of preserving an
// artificial Voronoi-like cut.
for (let pass = 0; pass < passes; pass++) {
rebuildLandscapeUnitAdjacency(unitId, units, fields.naturalBarrierScore, prefectureMask, sea);
let best = null;
let bestScore = 0.0;
for (const unit of units) {
if (!unit || unit.area === 0) continue;
for (const [otherId, edge] of unit.adjacent) {
if (otherId <= unit.id) continue;
const other = units[otherId];
if (!other || other.area === 0) continue;
const avgBarrier = edge.target / Math.max(1, edge.count);
const sameClass = unit.classId === other.classId;
const sameGroup = naturalGroupKey(unit) === naturalGroupKey(other);
const combinedArea = unit.area + other.area;
if (!sameClass && !sameGroup) continue;
if (combinedArea > maxMergedArea && unit.area > 18 && other.area > 18) continue;
const h = edge.horizontal || 0;
const v = edge.vertical || 0;
const balancedStair = Math.min(h, v) / Math.max(1, h + v);
const lowlandContinuity = Math.min(unit.lowlandFitness || 0, other.lowlandFitness || 0);
const mountainContinuity = Math.min(unit.mountainFitness || 0, other.mountainFitness || 0);
const urbanGuard = Math.max(unit.urbanWeight || 0, other.urbanWeight || 0);
const weakDivider = avgBarrier < (sameClass ? 0.46 : 0.36);
if (!weakDivider) continue;
const score =
(sameClass ? 1.7 : 0) +
(sameGroup ? 1.2 : 0) +
balancedStair * 1.4 +
edge.count * 0.018 +
lowlandContinuity * 0.85 +
mountainContinuity * 0.35 -
avgBarrier * 4.8 -
Math.max(0, combinedArea - maxMergedArea) * 0.020 -
urbanGuard * 0.20;
if (score > bestScore) best = { fromId: unit.area <= other.area ? unit.id : otherId, toId: unit.area <= other.area ? otherId : unit.id }, bestScore = score;
}
}
if (!best) break;
mergeUnitInto(unitId, units, best.fromId, best.toId, fields);
}
}
function naturalOwnershipAffinity(unit, neighbor, edge) {
const boundaryTarget = edge.target / Math.max(1, edge.count);
const sameClass = unit.classId === neighbor.classId ? 1.0 : 0;