Substancia's map, its region names, and its "nearest kin" rankings all come from a deterministic pipeline over PsychonautWiki's public wiki content — TF–IDF vectors, cosine similarity, UMAP, graph community detection, and a rule-based extractor over pharmacology prose. No language model touches the dataset or the layout.
Every specimen starts as a scraped PsychonautWiki article — a community-edited wiki, not a clinical database. That's a real constraint, not a footnote: coverage is uneven, some articles are stubs, and the structured "effects" a substance carries reflect what editors documented, not a controlled study.
Scraped effect names arrive damaged two ways. Unparsed wiki-link syntax leaves real effects
wearing a broken prefix ([[Brightness alteration), and unfilled infobox templates
leave placeholder strings that name nothing (Cognitive effect1). Junk names are
rare by construction, so left alone they'd get weighted enormously by the very statistic
(below) that's supposed to find what's distinctive — a handful of substances would get
pulled into spurious clusters built entirely out of scraping damage. Normalisation happens
before vectorisation, not after.
A second, corpus-wide pass folds spelling variants together — names that differ only by punctuation or a trailing plural collapse to one canonical spelling, chosen deterministically so the same input always produces the same output:
def _morphological_key(name: str) -> str:
s = re.sub(r"[,\-–—]", " ", name.lower())
s = re.sub(r"[^a-z0-9 ]", "", s)
words = [w[:-1] if len(w) > 3 and w.endswith("s") else w for w in s.split()]
return " ".join(w for w in words if w)
This is deliberately orthographic, not semantic. PsychonautWiki redirects "Ego death" and "Memory suppression" to the same article, but merging those is a judgement about phenomenology, not a spelling fix — so they're left apart.
Each substance becomes a vector across all 314 canonical effects, weighted by inverse document frequency — an effect nearly every substance reports carries almost no information about what makes a substance distinct, while a rare one is highly diagnostic:
Two substances' similarity is the cosine of the angle between their weighted vectors, not the distance — a thoroughly-documented substance has a longer vector without being different in kind, and cosine ignores that, comparing direction instead of magnitude:
The 2D layout comes from UMAP over that similarity space:
The fixed seed means the same corpus always produces the same map — without it, screenshots and documentation would drift out of sync with what you actually see.
The map shows what's close together but not why, so region names come from clustering the similarity graph directly — each substance connected to its 8 most similar neighbours — rather than the projected 2D coordinates, so the grouping doesn't inherit distortion the projection introduces. Greedy modularity maximisation over that graph produces 8 communities.
Each region is then labelled from its own members: their weighted effect scores are summed, and the six highest-weighted effects become the label. Summing weighted rather than raw counts is what makes labels like frame rate suppression or memory displacement come out instead of whatever's simply common everywhere.
The "binds like" panel comes from a rule-based extractor that reads receptor and transporter mentions directly out of each article's Pharmacology section — forty target patterns matched per sentence, each scored and signed from the nearest directional cue. Three failure modes needed explicit handling:
Because this is heuristic, it's measured rather than assumed — a hand-curated ground-truth set drives an accuracy harness, and the build fails if extraction accuracy falls below a set floor.
Every specimen carries two independent similarity rankings — one from reported subjective effects, one from extracted receptor targets. The Divergence panel is simply the two compared:
A substance can feel like another without sharing its pharmacology, or share pharmacology without feeling alike — both are real, and the panel exists to show which one you're looking at, rather than quietly picking one.
Two limitations worth knowing before you trust a result at face value:
UMAP only guarantees local neighbourhood preservation, not that every true nearest pair stays visually adjacent in 2D. Concretely: Pregabalin and Gabapentin are each other's #1 nearest neighbour in the actual similarity graph (cosine 0.61, one of the strongest pairings in the dataset) — but they land in different named regions on the map, roughly 2.7 units apart on a map spanning about 26. The ranked Nearest Kin list on each specimen's own page is the authoritative answer; pixel distance on the Firmament is an approximation of it, not a replacement.
Community boundaries can cut through locally continuous space the same way — Kratom sits map-adjacent to Tapentadol (an opioid, its actual #1 neighbour) but the two fall into different named regions.
Only 21 of 202 substances have four or more extracted targets. Below that, cosine similarity on a near-empty vector produces meaningless matches — during development, a substance with a single extracted target came out "binding like" an unrelated compound at a similarity of 1.00, which is an artefact of sparse data dressed up as a pharmacological claim. Both the binding-neighbour list and the divergence panel are gated at a four-target minimum on both sides; below that, nothing is shown rather than something false. The honest result is that 181 of 202 substances currently show no binding relatives at all.