This commit is contained in:
33333-33333 2026-05-26 00:45:01 +09:00
commit 5c82bfcab7
13 changed files with 1028 additions and 8801 deletions

36
test.js
View file

@ -1,7 +1,6 @@
import { generateMap, MAP_W, MAP_H, indexOf } from "./mapGenerator.js";
import {
CUSTOM_NAMES,
FORCED_NAMES,
CUSTOM_NAME_LIST,
NAME_KANJI_POOLS,
NAME_PARTS,
NAME_PROBABILITIES,
@ -9,6 +8,7 @@ import {
NAME_TEMPLATE_WEIGHTS,
generateEntityName,
generateTemplateName,
validateGeneratedName,
} from "./names.js";
const result = document.getElementById("result");
@ -654,6 +654,7 @@ try {
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) => validateGeneratedName(item.name, { allowAsciiDiagnostic: true }).valid), "generated names pass place-name validation");
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(map.nameDebug && Array.isArray(map.nameDebug.emptyPools), "nameDebug reports empty pools");
@ -664,14 +665,12 @@ try {
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,
map.nameDebug.generatedNamesUsed + map.nameDebug.customNameListUsed + map.nameDebug.fallbackAttempts === namedEntityCount,
"nameDebug accounting covers named entities"
);
assert(activePoolChars.size > 0 || generateTemplateName(777, "probe-0", { x: 10, y: 10, kind: "Probe" }, {}, 0, new Set()) === null, "template generation depends on active pools");
assert(villageClusterMean > 0.16, "villages prefer clustered valley, basin, coastal, and agricultural cells");
assert(saneEndpointRatio >= 0.76, "transport endpoints stay near meaningful generated nodes");
assert(Object.keys(CUSTOM_NAMES).length === 0 || NAME_PROBABILITIES.customName < 1, "CUSTOM_NAMES are probabilistic by default");
assert(
map.adminCenters.length !== other.adminCenters.length ||
map.villages.length !== other.villages.length ||
@ -751,28 +750,13 @@ try {
.map((seeded) => ({ deposition: seeded.terrainTemplate.deposition, lowlandRatio: terrainCoreMetrics(seeded).lowlandRatio }))
.sort((a, b) => a.deposition - b.deposition);
assert(byDeposition[byDeposition.length - 1].lowlandRatio >= byDeposition[0].lowlandRatio * 0.72, "higher-deposition templates generally preserve or expand lowland area");
CUSTOM_NAME_LIST.push("L1", "L2");
const listedCustomNames = Array.from({ length: 50 }, (_, n) => generateEntityName(9200 + n, `list-probe-${n}`, { x: 10, y: 10, kind: "Probe" }, {}, new Set()));
const listedCustomHits = listedCustomNames.filter((name) => name === "L1" || name === "L2").length;
assert(NAME_PROBABILITIES.customNameList > 0 && listedCustomHits > 0 && listedCustomHits < listedCustomNames.length, "CUSTOM_NAME_LIST supplies probabilistic selected place names");
CUSTOM_NAME_LIST.length = 0;
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"];
CUSTOM_NAMES["custom-probe"] = "C1";
const directCustomNames = Array.from({ length: 40 }, (_, n) => generateEntityName(9000 + n, "custom-probe", { x: 10, y: 10, kind: "Probe" }, {}, new Set()));
const directCustomHits = directCustomNames.filter((name) => name === "C1").length;
assert(NAME_PROBABILITIES.customName > 0 && NAME_PROBABILITIES.customName < 1 && directCustomHits > 0 && directCustomHits < directCustomNames.length, "CUSTOM_NAMES are probabilistic suggestions");
delete CUSTOM_NAMES["custom-probe"];
FORCED_NAMES["forced-probe"] = "F1";
assert(generateEntityName(123, "forced-probe", { x: 8, y: 8, kind: "Probe" }, {}, new Set(), map.nameDebug) === "F1", "FORCED_NAMES always apply");
delete FORCED_NAMES["forced-probe"];
assert(!validateGeneratedName("青青").valid && validateGeneratedName("青青").reason === "repeatedKanji", "place names reject repeated kanji");
for (const seed of [101, 2026, 54321]) {
const seeded = generateMap(seed);