new name system?
This commit is contained in:
parent
eb5c4a93a9
commit
db8265384c
5 changed files with 525 additions and 232 deletions
94
test.js
94
test.js
|
|
@ -1,11 +1,26 @@
|
|||
import { generateMap, MAP_W, MAP_H, indexOf } from "./mapGenerator.js";
|
||||
import { CUSTOM_NAMES } from "./names.js";
|
||||
import {
|
||||
CUSTOM_NAMES,
|
||||
FORCED_NAMES,
|
||||
NAME_KANJI_POOLS,
|
||||
NAME_PARTS,
|
||||
NAME_PROBABILITIES,
|
||||
NAME_TEMPLATES,
|
||||
NAME_TEMPLATE_WEIGHTS,
|
||||
generateTemplateName,
|
||||
} from "./names.js";
|
||||
import { adminBoundaryMetrics, majorCityCoreIntegrity } from "./qualityMetrics.js";
|
||||
|
||||
const result = document.getElementById("result");
|
||||
const logLines = [];
|
||||
let failed = 0;
|
||||
|
||||
const [namesSource, mapGeneratorSource, testSource] = await Promise.all([
|
||||
fetch("./names.js").then((response) => response.text()),
|
||||
fetch("./mapGenerator.js").then((response) => response.text()),
|
||||
fetch("./test.js").then((response) => response.text()),
|
||||
]);
|
||||
|
||||
function assert(condition, message) {
|
||||
if (condition) logLines.push(`OK: ${message}`);
|
||||
else {
|
||||
|
|
@ -79,16 +94,26 @@ try {
|
|||
const allNameable = map.entitiesForNames || [];
|
||||
const uniqueNames = new Set(allNameable.map((item) => item.name));
|
||||
const duplicateNameRatio = allNameable.length ? 1 - uniqueNames.size / allNameable.length : 0;
|
||||
const coastalSuffixes = ["\u6d5c", "\u6e4a", "\u6e2f", "\u6d66", "\u6d25", "\u5d0e", "\u6e7e"];
|
||||
const mountainSuffixes = ["\u8c37", "\u5ce0", "\u5c3e\u6839", "\u5c71", "\u6ca2", "\u9e93"];
|
||||
const farInlandCoastalNames = allNameable.filter((p) => {
|
||||
const i = indexOf(p.x, p.y);
|
||||
return !p.portClass && (map.coastalLowland[i] || 0) < 0.12 && coastalSuffixes.some((suffix) => p.name?.endsWith(suffix));
|
||||
}).length;
|
||||
const flatCoastalMountainNames = allNameable.filter((p) => {
|
||||
const i = indexOf(p.x, p.y);
|
||||
return (map.coastalLowland[i] || 0) > 0.35 && (map.slope[i] || 0) < 0.18 && mountainSuffixes.some((suffix) => p.name?.endsWith(suffix));
|
||||
}).length;
|
||||
const activePoolChars = new Set(Object.values(NAME_KANJI_POOLS).flat().flatMap((part) => Array.from(String(part))));
|
||||
const namedEntityCount = [
|
||||
...map.villages,
|
||||
...map.ports,
|
||||
...map.crossings,
|
||||
...map.passes,
|
||||
...map.markets,
|
||||
...map.castles,
|
||||
...map.castleTowns,
|
||||
...map.modernCities,
|
||||
...map.stations,
|
||||
...map.industrialZones,
|
||||
...map.interchanges,
|
||||
...map.logisticsParks,
|
||||
...map.satelliteCities,
|
||||
...map.newTowns,
|
||||
...map.castleRuins,
|
||||
...map.externalGateways,
|
||||
...map.adminCenters,
|
||||
].filter((item) => item?.id && item?.name).length;
|
||||
const villageClusterMean = map.villages.length
|
||||
? map.villages.reduce((sum, p) => sum + (map.settlementCluster?.[indexOf(p.x, p.y)] || 0), 0) / map.villages.length
|
||||
: 0;
|
||||
|
|
@ -119,6 +144,20 @@ try {
|
|||
const adminMetrics = adminBoundaryMetrics(map);
|
||||
const cityCoreIntegrity = majorCityCoreIntegrity(map);
|
||||
|
||||
assert(NAME_KANJI_POOLS && Array.isArray(NAME_KANJI_POOLS.modifiers), "NAME_KANJI_POOLS exists");
|
||||
assert(NAME_TEMPLATES && NAME_TEMPLATES.modifierTerrain?.slots?.length === 2, "NAME_TEMPLATES exists");
|
||||
assert(NAME_TEMPLATE_WEIGHTS && NAME_TEMPLATE_WEIGHTS.generic?.modifierTerrain > 0, "NAME_TEMPLATE_WEIGHTS exists");
|
||||
assert(NAME_PROBABILITIES && NAME_PROBABILITIES.contextCategoryWeights?.generic, "NAME_PROBABILITIES exists");
|
||||
const removedContextModule = "placeName" + "Context.js";
|
||||
assert(!namesSource.includes(removedContextModule) && !mapGeneratorSource.includes(removedContextModule) && !testSource.includes(removedContextModule), "removed name-context import is absent");
|
||||
assert(Object.keys(NAME_KANJI_POOLS).every((key) => Array.isArray(NAME_KANJI_POOLS[key])), "name category pools are centralized arrays");
|
||||
assert(Object.values(NAME_KANJI_POOLS).every((pool) => pool.length === 0), "default name category pools are empty");
|
||||
assert(Object.keys(NAME_PARTS).length === 0, "legacy NAME_PARTS has no hidden candidates");
|
||||
const removedContextSuffixConst = "CONTEXT" + "_SUFFIXES";
|
||||
const removedContextSuffixKey = "context" + "Suffixes";
|
||||
assert(!namesSource.includes(removedContextSuffixConst) && !namesSource.includes(removedContextSuffixKey), "hidden context suffix arrays are absent");
|
||||
assert(!/export\s+const\s+NAME_PROBABILITIES[\s\S]*export\s+const\s+NAME_PROBABILITIES/.test(namesSource), "NAME_PROBABILITIES has one source");
|
||||
|
||||
assert(map.elevation.length === size, "elevation length matches map size");
|
||||
assert(map.sea.length === size, "sea length matches map size");
|
||||
assert(map.river.length === size, "river length matches map size");
|
||||
|
|
@ -204,17 +243,24 @@ try {
|
|||
assert(capitalInside, "prefectural capital is inside the prefecture");
|
||||
assert(maxModernEndpointDegree <= 10, "modern transport endpoints are not over-centralized");
|
||||
assert(map.entitiesForNames.every((item) => item.id && item.name), "nameable entities have ids and names");
|
||||
assert(map.entitiesForNames.every((item) => typeof item.name === "string"), "nameable entity names are strings");
|
||||
assert(map.entitiesForNames.every((item) => !String(item.name).includes("\uFFFD")), "generated names contain no replacement characters");
|
||||
assert(map.entitiesForNames.every((item) => Array.from(String(item.name)).length >= 2), "one-character generated names are prevented");
|
||||
assert(map.entitiesForNames.every((item) => String(item.name).length > 0), "empty generated names are prevented");
|
||||
assert(duplicateNameRatio < 0.18, "generated place-name duplicates stay low");
|
||||
assert(farInlandCoastalNames <= Math.max(2, Math.ceil(allNameable.length * 0.12)), "coastal suffixes are not overused far inland");
|
||||
assert(flatCoastalMountainNames <= Math.max(2, Math.ceil(allNameable.length * 0.10)), "mountain suffixes are not overused on flat coastal lowlands");
|
||||
assert(map.nameDebug && Array.isArray(map.nameDebug.emptyPools), "nameDebug reports empty pools");
|
||||
assert(map.nameDebug.emptyPools.length === Object.keys(NAME_KANJI_POOLS).length, "empty default pools are visible in nameDebug");
|
||||
assert(map.nameDebug.selectedTemplateCounts && typeof map.nameDebug.selectedTemplateCounts === "object", "nameDebug selectedTemplateCounts exists");
|
||||
assert(map.nameDebug.selectedContextCounts && typeof map.nameDebug.selectedContextCounts === "object", "nameDebug selectedContextCounts exists");
|
||||
assert(
|
||||
map.nameDebug.generatedNamesUsed + map.nameDebug.customNamesUsed + map.nameDebug.forcedNamesUsed + map.nameDebug.fallbackAttempts === namedEntityCount,
|
||||
"nameDebug accounting covers named entities"
|
||||
);
|
||||
assert(generateTemplateName(777, "probe-0", { x: 10, y: 10, kind: "Probe" }, {}, 0, new Set()) === null, "empty pools do not use hidden fallback candidates");
|
||||
assert(activePoolChars.size === 0, "no active pool characters exist until configured");
|
||||
assert(villageClusterMean > 0.16, "villages prefer clustered valley, basin, coastal, and agricultural cells");
|
||||
assert(saneEndpointRatio >= 0.76, "transport endpoints stay near meaningful generated nodes");
|
||||
if (Object.keys(CUSTOM_NAMES).length > 0) {
|
||||
assert(map.entitiesForNames.every((item) => !CUSTOM_NAMES[item.id] || item.name === CUSTOM_NAMES[item.id]), "CUSTOM_NAMES override generated names");
|
||||
} else {
|
||||
assert(true, "CUSTOM_NAMES override hook remains available");
|
||||
}
|
||||
assert(Object.keys(CUSTOM_NAMES).length === 0 || NAME_PROBABILITIES.customName < 1, "CUSTOM_NAMES are probabilistic by default");
|
||||
|
||||
assert(
|
||||
map.adminCenters.length !== other.adminCenters.length ||
|
||||
|
|
@ -230,6 +276,18 @@ try {
|
|||
assert(JSON.stringify(againA.adminCenters.map((item) => [item.id, item.x, item.y, item.name])) === JSON.stringify(againB.adminCenters.map((item) => [item.id, item.x, item.y, item.name])), "municipal centers are deterministic for the same seed");
|
||||
assert(JSON.stringify([...againA.adminId]) === JSON.stringify([...againB.adminId]), "municipal adminId snapping is deterministic for the same seed");
|
||||
|
||||
CUSTOM_NAMES["city-0"] = "C1";
|
||||
const customSameA = generateMap(321);
|
||||
const customSameB = generateMap(321);
|
||||
const sameTargetA = customSameA.modernCities.find((item) => item.id === "city-0");
|
||||
const sameTargetB = customSameB.modernCities.find((item) => item.id === "city-0");
|
||||
const customSeedMaps = [301, 302, 303, 304, 305, 306, 307, 308].map((seedValue) => generateMap(seedValue));
|
||||
const customTargets = customSeedMaps.map((seeded) => seeded.modernCities.find((item) => item.id === "city-0")).filter(Boolean);
|
||||
const customHits = customTargets.filter((item) => item.name === "C1").length;
|
||||
assert(sameTargetA?.name === sameTargetB?.name, "custom-name probability is deterministic for the same seed");
|
||||
assert(!FORCED_NAMES["city-0"] && customTargets.length > 0 && customHits < customTargets.length, "CUSTOM_NAMES do not force every seed");
|
||||
delete CUSTOM_NAMES["city-0"];
|
||||
|
||||
for (const seed of [101, 2026, 54321]) {
|
||||
const seeded = generateMap(seed);
|
||||
const metrics = adminBoundaryMetrics(seeded);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue