Design specification: the mathematical and linguistic journey to give unique, pronounceable, human-sounding names to 55 million unnamed astronomical objects.
Of the 22 million galaxies now catalogued across GalaxyDB, fewer than a thousand carry a name that a person can actually say — Andromeda, Triangulum, the Sombrero. Every other galaxy, quasar, and deep-sky object in every catalog humanity has ever compiled carries only a designation — SDSS J001922.71+284829.2. 2MASX J04414489+2301513. NGC 7619. These designations are precise, essential, and completely impossible to remember or say.
A living catalog needs both kinds of names. The catalog designation preserves the discovery record. The given name is what a person actually uses. CORE exists to give every observable object the second kind of name — one built for human mouths and human memory.
| Object type | Count | Already named | Need CORE names |
|---|---|---|---|
| Galaxies | 22,431,182 | ~1,000 | ~22,430,000 |
| Stars (Milky Way) | 16,120,000 | ~450 | ~16,119,550 |
| Variable stars, pulsars, black holes, exoplanets, and more | ~17,000,000+ | ~0 | ~17,000,000+ |
| Total | 55,000,000+ | 55,000,000+ |
The engineering challenge: generate over one billion names that are (a) globally unique, (b) pronounceable in any human language, (c) dignified and memorable, and (d) deterministic — the same object always receives the same name regardless of when or where the engine runs.
Each iteration below solved the previous iteration's primary failure. The full sequence is documented here so future engineers understand why the final design is shaped the way it is.
The first engine used small, handcrafted root pools combined with simple connectives. It proved the concept. The names were pleasant. But the pool was never large enough to cover more than a few thousand objects, and the architecture was not designed to scale.
Not scalable Covered thousands, not millionsThe second system was architecturally ambitious. A pool of 422 carefully chosen roots, 11 binding phonemes, and a four-component combination engine. The mathematics were sound: 826 million unique name positions — more than enough to cover every object in the catalog with room for future discoveries.
The first hundred thousand names were beautiful. Roots drawn from ancient linguistic stocks combined through binding phonemes into names that felt genuinely old and real. The engine was working.
826 million names: ✓ Failed: gibberish after first few millionAs the scattering function worked deeper into the name space, something became apparent. The 422 roots had inconsistent syllable depths. Some roots were one syllable. Others were two. When multi-syllabic roots combined with a binding phoneme and another multi-syllabic root, names ballooned to five and six syllables of dense, adjacent consonant clusters. The mathematical contract — 826 million unique names — was honored. The human contract — names a person could say — was broken. This was not a refinement problem. It was a structural one.
The solution arrived from an unexpected direction: stop building names like a machine, and start building them like a person. Human naming traditions across every culture share a hidden structure. Names have gender. They carry suffixes that mark that gender — the -a of Elena, the -us of Marcus, the -or of Hector. And many traditions carry prefixes that encode relationship — the Di- of Italian diminutives, the El- of elder and origin.
We abandoned the root pool. We abandoned the binding phonemes. We replaced them with three clean, structured syllable pools governed by one phonotactic rule: every junction between syllables must be vowel-to-consonant. No consonant clusters. No unpronounceability. No gibberish.
1.1 billion unique names Every name pronounceable Emergent family relationshipsPool A and Pool B draw from the same set of 80 open CV syllables — short syllables that always end in a vowel. Pool C draws from 950 closed CVC syllables that always begin with a consonant. This guarantees a clean vowel-to-consonant junction at every point in the name.
| Category | Count | Suffixes |
|---|---|---|
| Feminine | 10 | -a, -e, -ine, -ella, -ina, -ana, -ira, -ora, -eia, -ova |
| Masculine | 10 | -us, -or, -on, -is, -ar, -ius, -an, -os, -en, -orn |
| Neutral | 6 | -el, -yl, -ix, -ex, -yn, -al |
| Prefix | Implied meaning |
|---|---|
| (none) | Base / original |
| Di- | Descent / child of |
| El- | Elder / progenitor |
| Re- | Renewal / recurrence |
| Ve- | Twin / mirror |
| Ko- | Shadow / companion |
| Ae- | Ancient / primordial |
The core phonotactic guarantee of CORE v3 is simple: every junction between syllables is vowel-to-consonant. Pool A and Pool B syllables are open — they always end in a vowel (a, e, i, o, u). Pool C syllables always begin with a consonant. The suffix always begins with either a vowel or consonant in a phonetically clean position.
This means:
No consonant clusters at junctions. No ambiguous breaks. Every name parses the same way in every language.
mo + Pool B la + Pool C rin + suffix -a = Molarina. With prefix Di-: Dimolarina. Every junction: vowel → consonant. Four syllables, cleanly spoken.
# Pool generation — Python
CV_ONSETS = ['b','d','f','g','h','j','k','l','m','n','p','r','s','t','v','w'] # 16
CV_VOWELS = ['a','e','i','o','u'] # 5
POOL_A = sorted({o+v for o in CV_ONSETS for v in CV_VOWELS}) # 80 syllables
CVC_ONSETS = CV_ONSETS + ['br','ch','cl','cr','dr','fl','fr','gl','gr',
'pl','pr','sh','sk','sl','sm','sn','sp','st','sw','th','tr','tw'] # 38
CVC_CODAS = ['l','n','r','s','t'] # 5 clean codas
POOL_C = sorted({o+v+c for o in CVC_ONSETS for v in CV_VOWELS for c in CVC_CODAS}) # 950
Total unique names = |Prefix| × |Pool A| × |Pool B| × |Pool C| × |Suffix|.
Names are assigned deterministically. A fixed seed (0xA57E4321) and a scatter multiplier (316,227,767 — verified coprime to TOTAL via GCD check) map each object's catalog position to a unique location in the name space. The same object always receives the same name. The seed is permanent; changing it renames every object in the catalog.
# Core scatter — simplified
def generate_name(obj_type, obj_id):
base_raw = (OFFSETS[obj_type] + obj_id) * _MIX_A % TOTAL
for attempt in range(8):
raw = (base_raw + attempt * 7_919) % TOTAL
p = raw % _NP; raw //= _NP # prefix index
s = raw % _NS; raw //= _NS # suffix index
c = raw % _NC; raw //= _NC # Pool C index
b = raw % _NA; raw //= _NA # Pool B index
a = raw % _NA # Pool A index
name = (PREFIXES[p] + POOL_A[a] + POOL_B[b] + POOL_C[c]).capitalize() + SUFFIXES[s]
if _is_clean(name): return name
return "Solaris" # fallback — never reached in production
Unlike the v2 system's curated root pool (which required hand-selecting 422 roots for phonological quality), the v3 pools are generated algorithmically from a small set of phonological rules. Quality is guaranteed structurally, not by curation.
Generated from 16 single-consonant onsets (b, d, f, g, h, j, k, l, m, n, p, r, s, t, v, w) × 5 simple vowels (a, e, i, o, u). All digraph onsets (ch, sh, br, etc.) are excluded to keep these syllables short and light. Every syllable is exactly 2 characters and ends in a vowel.
ba be bi bo bu da de di do du fa fe fi fo fu ga ge gi go gu ha he hi ho hu ja je ji jo ju ka ke ki ko ku la le li lo lu ma me mi mo mu na ne ni no nu pa pe pi po pu ra re ri ro ru sa se si so su ta te ti to tu va ve vi vo vu wa we wi wo wu
Generated from 38 onsets (the 16 single consonants plus 22 digraphs: br, ch, cl, cr, dr, fl, fr, gl, gr, pl, pr, sh, sk, sl, sm, sn, sp, st, sw, th, tr, tw) × 5 vowels × 5 codas (l, n, r, s, t). The codas are chosen for their clean, universal phonology — every language with consonant codas uses at least some of these five.
| Pool | Pattern | Size | Examples |
|---|---|---|---|
| A | CV | 80 | ba, de, ko, ri, vo, mo, la, vi… |
| B | CV (same pool) | 80 | ge, na, su, ti, fo, ru, pi, we… |
| C | CVC | 950 | vel, rin, mor, sten, brat, chor, plin, thon… |
Each of the 22 object types occupies a reserved, non-overlapping slice of the 1.1 billion-name space. Objects within a type are offset from the start of their slice. No two objects of any type ever share a name.
| Object type | Offset start | DB |
|---|---|---|
| Stars | 0 | StarDB |
| Variable stars | 50,000,000 | StarDB |
| Galaxies | 70,000,000 | GalaxyDB |
| Quasars | 150,000,000 | GalaxyDB |
| Deep sky objects | 170,000,000 | GalaxyDB |
| Asteroids | 175,000,000 | StarDB |
| Binary systems | 177,000,000 | StarDB |
| Multiple systems | 178,000,000 | StarDB |
| X-ray sources | 178,500,000 | StarDB |
| Satellites | 179,500,000 | StarDB |
| Meteors | 180,000,000 | StarDB |
| Exoplanets | 180,200,000 | StarDB |
| Supernovas | 180,300,000 | StarDB |
| Comets | 180,400,000 | StarDB |
| Star clusters | 180,450,000 | StarDB |
| Pulsars | 180,500,000 | StarDB |
| Supernova remnants | 180,550,000 | GalaxyDB |
| Moons | 180,560,000 | StarDB |
| Neutron stars | 180,570,000 | StarDB |
| Black holes | 180,580,000 | StarDB |
| Magnetars | 180,590,000 | StarDB |
| Constellations | 180,595,000 | StarDB |
_SEED = 0xA57E4321) must never be changed. The scatter multiplier (_MIX_A = 316,227,767) is verified coprime to TOTAL at startup. Changing either value renames every object in the catalog and must be treated as a breaking change requiring a full re-run across all tables.
Consider Ellenopeia — a genuinely beautiful name, and one the CORE engine would be happy to assign to a galaxy. It is lovely and elegant all at the same time. The same stem with the feminine suffix swapped for one that closes on a different consonant cluster, however, produces something else entirely. The suffix -peia (pronounced "pea-ya") is a perfectly ordinary closed two-step syllable. However, if the engine happened to drop the last two letters of the name — that is, the ia — and combined the prior prefix with certain suffixes that begin with the last three letters of a word such as Lunis or Beltonis... then we get a name that no astronomer wants associated with their discovery. This result we found to be extremely instructive — and also quite funny.
The first full test run of v3 made this exact lesson quite vivid. Ellenopeia was fine. However, the alternative derivation as explained above was not so good. The variant produced from the beginning of Ellenopeia and the ending of Beltonis was educational in a way that made the need for a filter immediately obvious to us. The engine was then tuned to completely reject such root combinations at the fundamental level, and this was done to ensure that no galaxy in the catalog would ever retain such a moniker for its name.
The filter covers all known inappropriate substrings: anatomical terms, vulgar language, slurs, and phonetic near-misses — names that, when pronounced aloud, resolve into something a catalog should not contain. Any candidate that fails the check is silently discarded. The engine retries up to eight times with a small internal offset before falling back to Solaris — a fallback that has never been reached in production.
Something unexpected happens in a name space of one billion slots. When two objects share the same syllable combination but receive different suffixes from the scatter function, they become — by pure mathematics — siblings. When one object's position lands on the Di- prefixed variant of another's stem, they become father and son. No curator assigned these relationships. The hash function discovered them.
The prefix meanings carry across all 22 types. El- marks an elder or progenitor. Re- marks renewal or recurrence. Ve- marks a twin or mirror. Ko- marks a shadow. Ae- marks the ancient and primordial. These are not assigned meanings — they are inherited ones, carried in from human naming tradition and now written into the structure of astronomical names for hundreds of millions of objects that were never meant to have names at all.
The following are actual output from the deployed CORE v3 engine — not hand-crafted examples. Each name is the deterministic result of its object's global position passing through the scatter function.