orbit/js/utils/NameGenerator.js

96 lines
3.4 KiB
JavaScript

import { config } from '../config/Config.js';
/**
* Deterministic name synthesis. Everything is drawn from the caller's
* Rng (so names are part of the seed), with syllable pools coming from
* data/naming.json. Code-level fallbacks keep this working even if the
* file is missing.
*
* const rng = Rng.derive(seed, 'name', 'S000123');
* const name = NameGenerator.star(rng); // "Kethavoryn"
*/
const FALLBACK = {
star: {
syllables: ['ka', 'vel', 'thu', 'ori', 'an', 'esh', 'mar', 'dy', 'neth', 'avi', 'cor', 'lu', 'tan', 'ys', 'brei', 'hal', 'ion'],
minParts: 2,
maxParts: 4,
},
planet: {
roots: ['aurel', 'bryn', 'cald', 'dross', 'emrys', 'fen', 'gale', 'hyra', 'ilv', 'jor', 'kest', 'lun', 'mora', 'neth', 'orb', 'pyre', 'quill', 'ross', 'sable', 'tarn', 'ulric', 'vex', 'wren', 'xan', 'yrra', 'zeph'],
},
galaxy: {
syllables: ['an', 'dro', 'me', 'mil', 'ky', 'cen', 'tau', 'rhi', 'sa', 'vel', 'pyr', 'os'],
minParts: 3,
maxParts: 5,
},
station: {
nouns: {
colony: ['Reach', 'Haven', 'Landing'],
miningStation: ['Rig', 'Dredge', 'Claim'],
cloudBase: ['Skyspire', 'Drift', 'Aerie'],
deepSpaceStation: ['Relay', 'Beacon', 'Anchor'],
waypoint: ['Waypoint', 'Beacon', 'Light'],
},
},
};
const ROMAN = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X', 'XI', 'XII', 'XIII', 'XIV', 'XV', 'XVI', 'XVII', 'XVIII', 'XIX', 'XX'];
function section(name, fallback) {
const v = config.get(`naming.${name}`);
return (v && typeof v === 'object') ? v : fallback;
}
function capitalize(s) {
return s.length ? s.charAt(0).toUpperCase() + s.slice(1) : s;
}
function joinSyllables(rng, syllables, minParts, maxParts) {
const parts = rng.int(minParts, maxParts);
let out = '';
for (let i = 0; i < parts; i++) out += rng.pick(syllables) ?? 'a';
return capitalize(out);
}
/** "1" → "I" … "20" → "XX"; beyond that, plain digits. */
export function ordinalLabel(n) {
return ROMAN[n - 1] ?? String(n);
}
export const NameGenerator = {
/** A star / system name: "Kethavoryn". */
star(rng) {
const s = section('star', FALLBACK.star);
const syllables = Array.isArray(s.syllables) && s.syllables.length ? s.syllables : FALLBACK.star.syllables;
return joinSyllables(rng, syllables, s.minParts ?? 2, s.maxParts ?? 4);
},
/** A planet root word: "Aurel" (compose the ordinal yourself). */
planetRoot(rng) {
const p = section('planet', FALLBACK.planet);
const roots = Array.isArray(p.roots) && p.roots.length ? p.roots : FALLBACK.planet.roots;
return capitalize(rng.pick(roots) ?? 'Novus');
},
/** A galaxy name: "Vethani". */
galaxy(rng) {
const g = section('galaxy', FALLBACK.galaxy);
const syllables = Array.isArray(g.syllables) && g.syllables.length ? g.syllables : FALLBACK.galaxy.syllables;
return joinSyllables(rng, syllables, g.minParts ?? 3, g.maxParts ?? 5);
},
/**
* A settlement name from its anchor + kind: "Kest Reach", "Cortan Relay".
* `anchorWord` is the planet's root word (planet-bound settlements) or the
* star's name (free-floating ones).
*/
station(rng, kind, anchorWord) {
const cfg = section('station', FALLBACK.station);
const all = cfg.nouns ?? FALLBACK.station.nouns;
const pool = Array.isArray(all?.[kind]) && all[kind].length ? all[kind] : (FALLBACK.station.nouns[kind] ?? ['Post']);
const noun = rng.pick(pool) ?? 'Post';
return `${anchorWord ?? 'Outer'} ${noun}`;
},
};