← Substancia
methodology · not an LLM

How the Firmament is built

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.

I

Source and scope

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.

288
specimens compiled
314
canonical effect names
202
have a receptor-target profile
II

Cleaning the vocabulary

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.

III

The map itself

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:

idf(t) = ln((N + 1) / (df(t) + 1)) + 1

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:

sim(a, b) = (va · vb) / (‖va‖ ‖vb‖)

The 2D layout comes from UMAP over that similarity space:

metric=cosine   n_neighbors=15   min_dist=0.3   random_state=42

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.

IV

Naming the regions

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.

V

Reading receptor targets from prose

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:

Tool compounds. "A selective 5-HT2A antagonist blocks the actions of DPT" is not a claim about DPT's own binding — naive matching would record it as one, backwards. These constructions are detected and discarded rather than inverted.
Multi-target sentences. "A 5-HT2A agonist with NMDA antagonist activity" names two targets with opposite directions in one sentence, so direction is resolved per mention by token distance, not per sentence.
Negation. "Does not bind," "no appreciable affinity," and "lacks activity at" all invert the reading and are matched out. Hedged claims ("may," "is thought to") aren't dropped, but reduce confidence, so an explicit adjacent cue outweighs a distant hedged one.

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.

95.8%
directional accuracy
31
pair ground truth set
85%
accuracy floor, build fails below it
VI

Where feeling and binding disagree

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:

divergence = effect_similarity − target_similarity

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.

VII

Where this breaks, stated plainly

Two limitations worth knowing before you trust a result at face value:

Map position isn't ground truth

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.

Receptor coverage is thin

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.