# The Machine That Listened
The short version of this story is that I made a rhyming dictionary. That is the version that fits on a line, and it is the version that survived, and it is wrong in the specific way that a photograph of a building's front door is wrong about the building. What I actually spent the decade between the mid-1990s and the mid-2000s doing was **constructing several different computable representations of the English language and then building an instrument that let a writer move between them without ever stopping to look anything up**. Rhyme was one of those representations. It was the deepest one, and the one that turned out to be marketable, and so it became the name of the thing. But the object I was building was not a dictionary. It was a **traversal engine over language**, and the dictionary was one of its published surfaces.
I want to set that down as the premise rather than deliver it as a revelation at the end, because the alternative structure — spend three thousand words on a rhyming dictionary and then announce that it was secretly something larger — is the structure of inflation, and this account does not need it. Everything below is discharge of that opening claim. The evidence is uneven: some of it is running code, some of it is surviving data files with timestamps inside them, some of it is a set of promotional screenshots from 2007, and some of it is my own memory, which I will mark as memory when I am relying on it. This is the last account I intend to write of the project, so I would rather it be checkable than impressive.
---
## I. Language as a thing with an address
I have never experienced being a writer and being a programmer as two occupations. They are one disposition applied to one object. Language is a system with structure that can be addressed, indexed, transformed, and operated upon, and the fact that it is also the medium in which human beings say what they mean has never seemed to me like an argument against treating it as infrastructure. It has seemed like the reason to.
I was running a BBS at ten. That matters here less as a credential than as a formative fact about what I thought a computer was for. A bulletin board is a text system. Everything that happens on it is language moving through storage and being retrieved by other people, and the whole craft of running one is the craft of organizing text so that it can be found. By the time I was doing anything that would be recognized as computational linguistics, I had already spent a decade thinking of writing as a **storage and retrieval problem** in addition to an expressive one.
The consequence, which shows up everywhere in what follows, is that I never approached a language question by asking what the answer was. I approached it by asking **what representation would make the question cheap**. That is a programmer's reflex applied to a writer's material, and it is the single most useful thing I brought to the work.
---
## II. Building the workshop first
Before any of the language work makes sense, there is a prior fact that explains how one person sustained it: I built my own tools first, and then I never rebuilt them.
Somewhere around the turn of the century I had accumulated a personal Perl standard library I called **shazam**. It is roughly two hundred and fifty modules, one function per file, organized under a fixed prefix taxonomy — `io_`, `string_`, `struct_`, `data_`, `net_`, `image_`, `datetime_`, `utils_`, `keydb_`, `cgi_` — with a generator script that concatenated them into a single importable `shazam-complete.pm`. Function names like `io_flatten_for_storage`, `string_extract_unique_words_and_numbers`, `struct_return_array_unique_items`, and `string_hash_cruncher_strict_alphanum_only` are not glamorous, and they are precisely the layer that made everything else possible. Every corpus pipeline I wrote for the next several years was **composition over primitives I had already written once**. CPAN existed. I wanted the primitives under my own hand, in a naming scheme I could hold in memory, with behavior I had personally decided.
Underneath that sat **KeyDB**, which was my own storage format and the module that read and wrote it. The convention is almost embarrassingly simple: one key-value record per line, pipe as the field separator, and — because linguistic data immediately destroys any naive flat format by containing the very characters you chose as delimiters — an escaping layer that encoded newlines and literal pipes as bracketed placeholders on the way in and reversed them on the way out. Around that I built exclusive and shared file locking, merge-and-write, overwrite, delete-key, delete-all-records-matching, increment and decrement, unique key assignment, bulk search-and-replace across keys or values, HTML escaping, and a parallel binary path using Perl's `Storable` for the cases where parse time mattered more than being able to read the file in a text editor. It is an **embedded key-value store with a full CRUD surface**, written because installing and administering a database engine was overhead I did not want in a program that had to run off a thumb drive.
The error-handling section of that module carries an annotation in my own hand: _Modified: Bryant — 4.27.2001_. That is the best hard date in this entire account, and it is worth more than any of the promotional material, because it is **internal to the artifact rather than inferred from where the artifact now sits**.
I mention all of this before the language work because it answers the obvious skeptical question. A decade of corpus engineering by one person is not plausible if each project starts from nothing. It is entirely plausible if the plumbing was amortized in advance, which is what the workshop was for. The through-line I described in the previous section — language as infrastructure — turns out to apply reflexively. I treated my own tooling the same way I treated English: as a system worth indexing properly once.
---
## III. The pipeline, and what was actually being built
The dictionary work began in 1995 and 1996, during my Borland period. The core of it was done then; what followed was roughly a decade of extension, correction, corpus growth, and productization, which is why the promotional material from the late 2000s could accurately say the software had been in development for over ten years.
What that decade mostly consisted of, in terms of hours, was **data engineering**. This is the part that gets omitted when the project is described as a dictionary, and it is the part where most of the intellectual work lived.
The shape of it is recoverable from the surviving directory structure, because I named things in a way that encodes the pipeline. Third-party raw material is filed under `wordlist <source>` — `wordlist cmudict`, `wordlist wordnet`, `wordlist moby mwords`, `wordlist 12dicts`, `wordlist lexical freenet old`. Canonical derived products are filed as `MASTER <THING>`. Runnable pipeline drivers get a leading exclamation point so they sort to the top and announce themselves as the things you execute rather than the things you import: `!Clean Strange Chars In Files.pl`, `!File Clean.pl`, `!Create Translation Dirs.pl`, `!Generate KeyDB Database and Indexes.pl`, `!Build MASTER ANAGRAMS.pl`. The polarity of the whole operation is written into the filenames. **Ingredients on one side, products on the other, and the scripts that convert between them clearly marked.**
One surviving driver, `process.pl`, is a good specimen of what the middle of that pipeline looked like. It loads fourteen source files through the shazam library, combines them, counts the result, and then applies a sequence of explicit eligibility rules: **keep entries containing spaces** — because the target was multiword material, not dictionary headwords — reject hyphenated entries, reject a particular leading pattern, drop anything under six characters, deduplicate the remainder through a hash, and write the survivors out. The commented-out sections are as informative as the live code, because they show stopword removal and sorting being tried and set aside. That restraint was deliberate: sophisticated rhyme phrases derive much of their usable sound structure from exactly the function words that ordinary information retrieval discards. _In the night_, _out of sight_, _for a while_ — strip the stopwords and you have destroyed the thing you were collecting.
Those fourteen source files encode their own record counts in their filenames, and they sum to approximately **6.35 million source records**. The output of that filtering was a normalized wordlist of about eleven megabytes. That ratio is the part I would point a hiring engineer at, more than any raw figure: nine hundred thousand usable phrase-level rhyme candidates were not scraped, they were the **product of a stated curation policy applied to six and a third million candidates**.
And the `MASTER` inventory itself is the most compact description of the project that exists. There is `MASTER RHYMES` and `MASTER RHYMES INDEX`. There is `MASTER RHYMES COMPOUND`. There is `MASTER SOUNDEX`. There is `MASTER SYNONYMS` and `MASTER SYNONYMS INDEX`. There is `MASTER TRIGGERS`. There is `MASTER ANAGRAMS`. That is not one dictionary. That is **six or seven distinct derived representations built over an overlapping lexical universe**, each optimized for a different question you might ask about the same words: what sounds like this, what approximately sounds like this, what means something adjacent to this, what stands in a taxonomic relation to this, what tends to occur near this in real human language, what can be spelled from this. A researcher reading that list today will recognize what it is without my having to say it, and I would rather they arrive at the resemblance themselves than have me assert it.
The same pipeline fed several different consumers, which is the other structural fact that the filenames give away. Alongside the scripts that generated the KeyDB databases and indexes for the application, there are scripts that generated static HTML and scripts that generated wiki files. The **fourteen-volume printed dictionary was a rendering target of the pipeline**, not a parallel lexicographic undertaking. One canonical derived core, several published surfaces — the Windows application, the books, the web pages. That is why a single person could produce fourteen volumes and a piece of software at the same time. They were the same artifact typeset differently.
[[IMG: book-cover-rhyme-v1-450w-front.jpg | Volume 1 of the printed McGill English Dictionary of Rhyme. The books and the software were the same derived dataset rendered to two different surfaces. ]]
---
## IV. The phonological engine
Here is the problem that started it, stated the way I actually experienced it rather than the way it appears in a specification.
I wanted a five-syllable word to rhyme against a phrase. Not against another five-syllable word — there usually is not one, and when there is, it is usually wrong. Against a **phrase**, because that is what the ear does. The ear does not know where the spaces are. It hears a sequence of sounds, and if a run of three short words produces the same terminal sound pattern as one long word, then they rhyme, and every songwriter and every rapper knows this and no rhyming dictionary I could buy would do it.
Stated as a computational problem, that is **approximate phonological sequence matching across variable lexical segmentation**. The target is a phoneme sequence. The candidate space is a corpus of nine hundred thousand multiword phrases. A match may distribute the target's sound across two, three, or four lexical tokens on the candidate side. Word boundaries are not the unit of the problem; **the phonological sequence is the unit of the problem**, and lexical segmentation is a property of the candidate rather than a constraint on the search. And when no exact alignment exists — which is most of the time, once the target is long — the system must not return nothing. It must degrade intelligently toward near rhyme, which means rhyme cannot be a binary predicate. It has to be a **graded correspondence with a ranking**, sensitive to how much of the sequence aligns and where the alignment sits relative to the stressed nucleus.
I have not seen my own implementation of that scoring in a long time and I am not going to reconstruct it from memory here, because a plausible reconstruction would be indistinguishable in the reading from a true one and I would rather leave a hole than fill it with something invented. What the surviving behavior proves, independent of the algorithm, is that **rhyme was represented internally as a continuum of alignment quality** rather than as membership in a fixed equivalence class.
The evidence for real phonology, as opposed to orthographic tail-matching, is visible in a single screenshot and it is the one I would put in front of a computational linguist first. Look up _dove_ and the program returns **two pronunciations as first-class tabs, each producing a disjoint rhyme set**. One yields _above, glove, love, shove, thereof_. The other yields _clove, cove, grove, jove, rove, stove, strove, trove_. A spelling-based system can discover that _love_ and _glove_ share a tail. It cannot handle _dove_, because _dove_ is a heteronym and the bird and the past tense of _dive_ are different words that happen to be spelled alike. Handling it correctly requires that a word be represented as **one or more pronunciations**, that rhyme classes be derived from the pronunciations rather than the spelling, and that pronunciation multiplicity propagate all the way through retrieval and into the interface so the writer can choose which word they meant. The status bar says it out loud: _2 pronunciations for 'dove'_.
[[IMG: screen13.jpg | Two pronunciations of _dove_, each with its own rhyme set. For this screen to be possible, the lexicon must store words as sets of pronunciations, derive rhyme classes phonologically rather than orthographically, and carry that multiplicity through retrieval into the interface. ]]
The phrase layer is where the multiword work becomes visible. Clicking _More_ opens the **Phrases & Proper Nouns** bank, and for the _dove/love/stove_ neighborhood it returns hundreds of entries — _Cottage Grove_, _Council Grove_, _Copperas Cove_, _bird of Jove_, _franklin stove_, _potbellied stove_, _kerosene stove_, _cook stove_. Real writing does not terminate at dictionary headwords. Titles, place names, personal names, and idiomatic multiword expressions all have phonological endings, and they are frequently the only thing that will actually land. Supporting them meant the retrieval model had to extend past a conventional word lexicon into a **multi-token lexical layer**, which is exactly what the six-million-record filtering pipeline in the previous section was manufacturing.
[[IMG: screen14.jpg | The Phrases & Proper Nouns bank. Supporting these requires a multi-token lexical layer with its own phonological indexing — a conventional headword lexicon cannot produce them. ]]
There is a subsystem called **Roundex** whose output sits in its own panel and looks like phonetic or fuzzy-neighborhood expansion. I named it, I built it, and I am not going to describe its algorithm, because I no longer remember it with the precision that a description would imply. It may have been part of the candidate-generation machinery that let the engine move outward from exact rhyme toward increasingly approximate ones, or it may have been a separate mechanism entirely. If someone reading this still has a binary or a source tree, I would like to know.
[[IMG: screen15.jpg | Split-panel display with Standard Rhymes, Context, Verse Forms, the editor, the Rhyme Lines corpus, Phrases & Proper Nouns, and the Roundex panel all live simultaneously. ]]
The syllable counter deserves more respect than it usually gets, because it looks trivial and is not. English syllable count is not reliably derivable from character patterns — silent vowels, diphthongs, contractions, possessives, abbreviations, proper names, and unknown words all defeat the obvious heuristics. With a pronunciation lexicon you can count vowel nuclei for most words, but any production system needs defensible fallback behavior for lexical gaps, because writers type words that are not in your dictionary constantly. And because the count updates **while you type**, the editor has to couple document-change events to tokenization and phonological lookup without perceptible delay. The engineering problem is not _count the syllables_. It is **maintain prosodic metadata as an interactive invariant of a changing text**.
[[IMG: screen6.jpg | Per-line syllable counts updating as you type. Every keystroke has to be routed to tokenization, pronunciation lookup, and a partial recount without perceptible latency. ]]
---
## V. The instrument
The design thesis of VersePerfect fits in one sentence that appears, more or less, on one of its own promotional screenshots: **no more interrupting your creative workflow to look up words**.
That sentence names the thing I was actually trying to destroy. Reference software of that era assumed a transaction. Stop writing. Decide which resource you need. Formulate a query. Open the resource. Read the answer. Return to the document. Reacquire your place, your intention, and your momentum, all of which have degraded. The **lookup transaction** is a tax on composition, and the tax is not the seconds — it is the eviction of whatever you were holding in your head.
So the system was built to infer the object of assistance from the editor state and to keep the relevant linguistic possibilities standing around the act of writing. The current token drives the rhyme results. The selected pronunciation determines which rhyme neighborhood is showing. Line edits update syllable counts. Structural position updates the verse-form annotation. Semantic context populates synonyms. And a literary corpus panel surfaces attested lines underneath all of it. Nothing is requested. The document is not passive text; typing modifies a **live computational state**, and the machine's job is to keep a continuously updated linguistic model of the document and of where the writer's attention currently is.
[[IMG: screen1.jpg | The main editor. Line numbers, red syllable counts, and green rhyme-class letters run down the gutter while the right-hand panels track the current token's pronunciations, rhymes, semantic context, and verse form — all simultaneously, all derived from editor state. ]]
[[IMG: screen2.jpg | The design thesis, stated on the software's own promotional annotation: rhymes and synonyms arrive as you type, so the lookup transaction never happens. ]]
**Verse forms** were the part where I stopped treating a poetic structure as explanatory text and started treating it as data. Selecting _Italian Sonnet_ projects the form into the document as a column of rhyme-class letters running beside the lines, synchronized with the syllable counts in the adjacent column. The program is not describing a sonnet; it is holding a **formal representation capable of assigning expected structural positions to successive lines** and displaying those expectations against what you have actually written. That is a small domain-specific grammar for poetic form, and the selector held dozens of them — Rime Royal, Rubaiyat, Sestina, Shakespearean and Spenserian sonnets, the Spenserian stanza, Tanka, Terza Rima, Villanelle, several quatrain variants. The presence of a form in the list proves template support; it does not by itself prove how deeply each one was validated, and the harder cases — the sestina's permutation schedule, the villanelle's refrains — are exactly where I would want to see the template definitions again before claiming enforcement rather than scaffolding.
[[IMG: screen3.jpg | The selected verse form projected into the document as a rhyme-class column beside the lines, synchronized with syllable counts. The form is stored as structure, not prose. ]]
[[IMG: screen3b.jpg | Dozens of classical and contemporary forms in the selector. Each entry implies a stored structural definition the editor can evaluate line positions against. ]]
Each form also carried human-readable description, instruction, and a worked example. Choosing _Haiku_ and opening the information window gives the definition, the seasonal convention, and an example with its 5/7/5 counts displayed — an example, as it happens, that I wrote. Pedagogy and constraint checking in the same object, which is a thing I would still argue for.
[[IMG: screen8.jpg | The verse-form information window: definition, instruction, and a worked example with syllable counts. Forms carried metadata, not just structure. ]]
**Rhyme Lines** was the corpus feature, and conceptually it is a different discipline from everything else in the program. Underneath the editor, continuously, the software surfaces complete lines from published poetry that relate to the current lexical and phonological context, with title and author attached — Tennyson's _All Things Will Die_ returning its hammering series of _must die_ lines, _The Lady of Shalott_, Dickinson, Sexton, Barrett Browning. This required ten thousand poems normalized, associated with authorship metadata, and annotated with rhyme data so that a retrieval key could connect the writer's current target to candidate lines fast enough to be ambient rather than requested. It is a specialized concordance operating **at the point of composition**, and its cognitive function is straightforward: not _give me a word_, but _show me how this language has actually been used by people who were good at it_.
[[IMG: screen4.jpg | Rhyme Lines returning attested usage from published poetry with author and title. Behind it: ten thousand poems normalized, metadata-associated, and rhyme-annotated for retrieval. ]]
The **Hyperbolic Thesaurus** rendered lexical relations as an explorable graph rather than a list — _love_ at the center, with _pleasure_, _gratification_, and _enjoyment_ as primary neighbors and their own descendants radiating outward, node coloring distinguishing focal state from depth. Semantic-network visualization was not my invention and I do not claim it; comparable products existed by the late 1990s. What was unusual was putting it **inside a working editor**, beside phonology and corpus search and formal verse constraint, so that a writer could move from a word to its sound neighbors, or to its semantic neighbors, or to its taxonomic superordinates, without changing applications or losing the document.
[[IMG: screen5.jpg | The Hyperbolic Thesaurus. Lexical relations rendered as traversable topology rather than an alphabetical list. ]]
The **Word Explorer** was the textual counterpart to that graph, and it is where the ontology work is most legible. Look up _illusion_ and it returns the word partitioned into numbered senses, with buttons switching between noun, verb, adjective, and adverb readings, and relational tabs labeled _Nearest_, _Synonyms_, _Type of_, _Types_, and _See also_. Those last two are the ones that matter. _Type of_ and _Types_ are **hypernym and hyponym traversal** — taxonomic relations, not flat synonymy — which means the semantic layer was operating on sense-disambiguated lexical relations rather than on a bag of similar words.
[[IMG: screen1a.jpg | Word Explorer on _illusion_: numbered senses, part-of-speech switching, and typed relations including hypernym and hyponym traversal. ]]
[[IMG: screen12.jpg | Word Explorer on _feel_, showing the same sense-partitioned, relation-typed structure. ]]
The **Online Lookup** subsystem federated external reference material through one interface. The controls are a `Database:` selector and a `Search Strategy:` selector, and the database list is long enough to be its own argument: the Collaborative International Dictionary of English, WordNet 2.0, Grady Ward's Moby Thesaurus II, an elements database, the Jargon File, the Free On-line Dictionary of Computing, Easton's and Hitchcock's Bible dictionaries, Bouvier's Law Dictionary, _The Devil's Dictionary_, the CIA World Factbook, the U.S. Gazetteers down to ZIP code tabulation areas, and then a long run of bilingual Freedict resources — Latin, Dutch, Portuguese, Scottish, Serbo-Croat, Slovenian, Spanish, Swahili, Swedish, Turkish. The vocabulary of that interface — _database_ plus _search strategy_, with definition lookup distinguished from strategy-based matching — maps closely onto the DICT protocol standardized as RFC 2229 in 1997. I would call a DICT-compatible backend a **strong inference rather than a settled fact** until someone reads the source or the network configuration, but it is considerably more than a resemblance. What it accomplished architecturally is the thing that matters regardless: a **common query abstraction over heterogeneous lexical resources and languages**, so the writer never had to learn each backend.
[[IMG: screen9.jpg | Online Lookup returning a formatted dictionary entry inside the application, with Database and Search Strategy as first-class controls. ]]
[[IMG: screen10.jpg | The database selector: dozens of heterogeneous reference resources exposed through one query abstraction. ]]
[[IMG: screen11.jpg | The bilingual resources continue for another screen, turning an English poetry tool into a federated multilingual lexical client. ]]
Spell checking sat on the same event stream, and I built the checker rather than delegating it, because the point was that **every linguistic subsystem should share one lexical universe**. A misspelling gets the wave underline and a context menu of corrections, exactly as it would in a word processor — but in this program the same keystroke that triggered the spelling check also updated the syllable count, the rhyme neighborhood, the verse-form annotation, the semantic panel, and the corpus retrieval. That convergence is what made it a **linguistically instrumented editor** rather than a dictionary with a text box attached.
[[IMG: screen7.jpg | Live spell checking. The same document-change event feeds spelling, syllabification, rhyme, verse structure, semantics, and corpus retrieval simultaneously. ]]
And then there were tabs for Wikipedia, Wiktionary, and Dictionary.com rendering directly inside the application, with the Rhyme Lines panel still alive underneath. Wikipedia had launched in 2001. The claim I would make about this is not that VersePerfect had Wikipedia — anyone could open a browser. It is that the interaction architecture treated the emerging web knowledge commons as **one more context surface inside a domain-specific creative workstation**, subject to the same rule as everything else: the writer does not leave the document.
[[IMG: screen16.jpg | Wikipedia rendered inside the writing environment, with the corpus panel still live beneath it. Another context surface, same rule: never leave the document. ]]
Finally, the dictionary existed as its own application, which is the screenshot that clarifies the relationship between the two products. The window titled **McGill English Dictionary of Rhyme 2.0** has its own tab structure — Combined, Side by Side, Roundex, Online Dictionary, Rhyme Lines, Hyperbolic Thesaurus — with Pronunciation 1 and Pronunciation 2 as first-class navigation and the Combined view merging standard rhymes with the phrase bank into one result set. VersePerfect did not have a small rhyme list bolted onto a word processor. **VersePerfect grew around an already substantial lexical engine.**
[[IMG: screen17.jpg | The dictionary as its own application, with pronunciation tabs and its own view modes. The editor was built around this engine, not the other way around. ]]
---
## VI. What it was actually for
Everything above describes a system that assists a human writer. The research program underneath it was asking a harder question, and this is the part of the work I care most about preserving.
It began with grammar. I had been building increasingly elaborate Chomsky-style generative bots, and they taught me the limitation of formal grammar directly rather than theoretically: **a sentence can be perfectly well formed and have nowhere to go**. Grammaticality gives you admissible structures. It does not give you a thought. If a machine is going to produce language that develops rather than merely continues, something has to supply the trajectory, and syntax cannot.
So I went looking for the trajectory in corpora, and what I found were **trigger relations** — pairs of terms where the appearance of one changes the relevance of the other somewhere in the surrounding discourse, each carrying a small numeric weight. These are not synonym pairs and they are not simple adjacent bigrams. _Galileo_ and _Jupiter_. _Hunting_ and _handgun_. _Selling_ and _stocks_. Alongside them, the messier texture of actual speech: _yes_ and _great_, _yep_ and _uh_, _she_ and _like_. Sentence-boundary tokens survive in the data, which is the giveaway that this material originated in statistical language modeling. My source files are named `!swb_t.txt` and `!bn_t.txt`, and the obvious reading is Switchboard and Broadcast News — a telephone conversation corpus and a broadcast transcript corpus. I am labeling that reading as reconstruction rather than fact until the toolkit documentation surfaces, but the content is consistent with it: the political and journalistic material has an unmistakable broadcast character and the _yeah/uh/she/husband_ material is unmistakably conversational.
The technology those triggers came out of was built to answer a narrow question — **which word becomes more probable given that another word occurred**. I was asking a much broader one. If linguistic association is treated as a **traversable structure**, can a machine follow human conceptual movement through language?
That reframing is the whole of _Living Language: Proximal Frequency Research Reference_, and the name is literal rather than decorative. **Proximal** meant proximity in observed language treated as evidence about conceptual relationship. Starting from a term, you follow associations outward into other regions of the corpus, and you weight the connections by combining **global occurrence**, **local occurrence**, and **degree of proximity**. Global occurrence gives the background rate, so you can discount a term that connects to everything. Local occurrence tells you whether a relation is unusually concentrated in the neighborhood you are currently traversing. Proximity tells you how tightly the two things actually co-occur. Together they let you distinguish a ubiquitous weak relationship from a rare but sharply localized one, which is the entire difference between noise and signal in association data.
What that produces is not a synset. I want to keep the distinction in my own terms because it was the point. A **synset** asks which lexical items participate in a particular already-classified sense. A **concept set** is a region of language that becomes visible by traversing repeated associations, and it is recovered from usage rather than assigned by a lexicographer. _Ocean_ does not merely lead to synonyms for ocean. Depending on the corpus it leads toward water, waves, coast, ships, depth, salt, horizon, distance, voyage, drowning, storms, memory, isolation. None of those is a synonym. All of them are where human beings actually go from _ocean_. And when an author supplies several primary terms — say _ocean_, _mother_, and _loss_ — the interesting region is not the union of three neighborhoods. It is the region where the three conceptual fields **begin to interact**, which is exactly where a poem might have something to say.
That is what the generator was for. The architecture I was assembling worked in this direction: the author establishes the subject by supplying primary concepts; the associative machinery expands those into weighted neighborhoods and traverses promising paths while maintaining relationship to the original subject and recent context; grammatical machinery produces candidate constructions; the pronunciation, rhyme, phrase-alignment, syllable, and verse-form systems constrain which realizations are admissible; and then the spelling checker and a rudimentary grammar checker I also wrote inspect the surface that actually came out, after all the other constraint systems have finished interfering with each other, and pull it back toward acceptable written English.
The name I would give that is **semantic-first constrained generation**, and the ordering is the argument. Most procedural poetry runs the other way: pick a rhyme, generate something syntactically plausible that arrives at it, repeat, and produce lines that scan and mean nothing. I wanted the **developing thought to come first** and rhyme to be a constraint on its expression. Which is also, incidentally, the real reason the multiword phrase work mattered. It was never only a better lookup for human poets. If a concept has to resolve into a particular sound and the system is not restricted to single dictionary words — if it can realize that sound across a phrase — then the **semantic engine has vastly more freedom to preserve the meaning while satisfying the constraint**. Nine hundred thousand phrases is not a bigger rhyme list. It is a bigger solution space for an inverse problem.
Every layer interferes with every other layer. The perfect rhyme destroys the meaning. The precise word destroys the meter. The natural construction goes nowhere conceptually. Solving one constraint reliably degrades three others. That is a **constraint-satisfaction problem over multiple simultaneous representations of language**, and I was trying to solve it before there was convenient machinery for expressing that kind of problem at all.
Part of that research came back out as a physical object. _Poet's Muse: Associative Reference for Writer's Block_ was typeset and produced as a print-on-demand book directly from the trigger databases — a corpus-derived association graph flattened onto paper, so that a writer stuck on a page could open it and be shown where language actually goes from where they are.
[[IMG: Poets_Muse_Front-resize.jpg | _Poet's Muse_ — the associative trigger databases rendered as a printed reference for writer's block. ]]
I should say plainly that I was not working in isolation from the field. The same period saw Doug Beeferman's Lexical FreeNet, which combined rhyme relations with semantic relations and which is in my source directories; the precursor to RhymeZone dates to early 1996. WordNet predated all of my work and I used it. Trigger-based language modeling was published out of Carnegie Mellon in 1993 and my trigger data descends from that lineage. Visual semantic-network products existed by the late 1990s. **I invented none of those things and the account is stronger for saying so.** What I did was take representations that lived in separate research communities, separate datasets, separate command-line tools, and separate products, and **synthesize them into one low-latency instrument**, and then push past assistance toward generation. The claim worth making is about **architecture**, not priority.
---
## VII. What survives
The application shipped, and shipped widely enough to leave third-party traces. Linux.com carried an item in December 2004 identifying me as editor and author of the fourteen-volume dictionary and describing the electronic release; I would treat that as establishing publication and contemporary description and nothing more, since the piece appears to be press-release-derived. In 2008, Softpedia's Madalina Boboc reviewed VersePerfect 2.0 with an editorial five out of five, and that one carries real weight, because the reviewer actually exercised the system — rhyme and definition search, import and export, the verse editor, syllable counting, spell checking, thesaurus suggestions, Word Explorer, Hyperbolic Thesaurus, multi-database lookup — and separately measured responsiveness, CPU use, and memory consumption. Professor Joseph Pisano, then Director of Music and Fine Arts Technology at Grove City College, wrote a substantial endorsement of it as a classroom tool. My own promotional material claimed more than five hundred thousand users, and I will mark that as my own historical claim rather than an audited figure. The software also accumulated a wall of twenty-six award badges from download portals, which I include here as **period texture rather than credential** — that is simply what software distribution looked like in 2007, and anyone who spent time in that ecosystem knows exactly how much those were worth.
The performance the reviewer measured is the direct consequence of the architecture described in section three, and I want to close the technical argument on it. VersePerfect ran from a folder. No installer, no dependency chain, no database engine, no configuration ceremony — unzip it to a thumb drive, run it, and get full-speed access to the entire language environment on whatever machine was in front of you. That was possible because **the expensive thinking had already been done**. The application did not discover relationships in language at query time; it consumed the output of years of offline preprocessing, organized into indexes and containers engineered for cheap retrieval. The intelligence was distributed across the human-designed representation, the pipeline that built it, the indexes that shaped it, and only lastly the runtime algorithms. That is why it felt instantaneous on hardware that had no business feeling instantaneous.
The application itself was a Delphi program, and its surviving component inventory tells a consistent story: an embedded local database for persistent structured storage, full-text indexing components for the corpus material, Perl-compatible regular expression machinery inside the compiled application, rich editor controls, a spelling engine, local HTML rendering, network components for the dictionary services, and archive components consistent with a product distributed as a self-contained zip.
The piece of that inventory that most directly explains the responsiveness is a container library called **DIContainers**, which I was using from the late 1990s and which my working Delphi tree retained in both source and Delphi 7 build form. It is worth being precise about what it was, because the naive assumption — that I was loading enormous textual dictionaries into memory at startup — is wrong and misdescribes the architecture. DIContainers was a **generic container framework for a version of Pascal that had no generics**, and its central design decision was to **separate the structure of a container from the type of the data inside it**. Items were memory-efficient Pascal records managed through handler objects responsible for initialization, finalization, copying, comparison, and streaming, which meant the ready-made classes were conveniences rather than limits: define a record and a handler and you could put an arbitrary application-specific structure — a pronunciation record, a phrase record, a corpus reference, a weighted association — inside any of the library's topologies. Those topologies were **hash tables, doubly-linked lists, N-ary trees, and vectors**, with assignment between representations so the same logical items were not trapped in one physical organization.
That is the missing half of the two-stage architecture. The Perl pipeline manufactured **already-reduced computational representations** offline; DIContainers supplied the **access geometry** into which those representations were materialized at runtime — keyed retrieval where the question was _what maps to this_, indexed vectors where the question was _scan this precomputed candidate set_, hierarchies where the question was taxonomic, ordered chains where the question was sequential. Expensive work upstream, cheap geometry downstream. I am describing what the library made architecturally natural rather than asserting which arrangement my code actually used; the `.pas` files would settle that almost class by class, since the units a source file imports would reveal the real in-memory data model directly. One boundary is worth stating for the same reason: DIContainers offered no native graph type. A graph had to be **built out of the primitives** — a hash of identifiers whose records carry vectors of weighted outgoing edges — which is precisely the shape the associative work in the previous section would have required, and precisely why that graph existed as an implicit structure long before I ever stored one in something that called itself a graph database.
The timing matters more than it first appears. I was not bolting containers on late, once the corpora had outgrown something naive; they were in the runtime from the beginning. Which means **the two-stage architecture was the original design rather than a subsequent optimization** — offline manufacture of reduced representations, runtime materialization into fast access geometry, from the first working version. The library's own published version history now begins in 2005 and its earlier generations have been pruned from the vendor's site, so that page cannot corroborate the early use; it is my recollection, and the later releases document features I should not project backward onto what I was actually compiling against. Which subsystem serviced which panel is likewise something I would want to confirm against source rather than assert from a component list.
As for what is actually recoverable now: I have the nineteen promotional screenshots, the old product page record, the Perl data-engineering scripts, my own KeyDB module with its 2001 timestamp inside it, the master database filenames, corpus remnants including the trigger sources, the component inventory, the review, the endorsement, and the book. I do not currently have the Delphi source in a state where I can read the rhyme engine. Until I do, the alignment algorithm, the Roundex mechanism, the exact scoring function, and the depth of verse-form validation remain **things I remember building rather than things I can show you**. I have tried throughout this account to keep those categories separate, and where I have written _I remember_, that is what I mean, and where I have written that an artifact is dated or that a reviewer tested something, that is a different and stronger kind of claim.
One present-tense fact, because everything else here is written in the past tense about software nobody can run. The pipe-delimited key-value format I wrote in Perl in 2001 for the dictionary pipeline is still serving my website today. **Twenty-five years, several languages, one storage convention, and the thing it stores is still language.**
If it were rebuilt, the temptation would be to make it the same thing on the web, and I think that would undersell what was actually discovered. The object worth resurrecting is not the feature list. It is the **linguistic workspace architecture** — a document surrounded by continuously updated representations of its sound, its meaning, its structure, its precedent, and its external knowledge. Embeddings, neural pronunciation models, local language models, and vector retrieval would extend that enormously, and they should sit downstream of the original idea rather than replacing it.
The symbolic layers would in fact be an asset now rather than a limitation, because they can make distinctions that generative systems blur. A rebuilt system could tell you, of any suggestion it offers, exactly what kind of thing it is: **this one is a phonological match; this one is a semantic relation; this one is a taxonomic relation; this one is attested in a corpus; this one satisfies the verse form you selected; this one is only a model's guess.** Provenance-sensitive multiplicity. That would make it a considerably more serious instrument than a generic machine that writes poems for you, and it is what I would build if I built it again.
The description I would leave behind, then, is not _he made a rhyming dictionary_. It is this: between the mid-1990s and the mid-2000s I built a **symbolic language engine** — a hand-engineered, corpus-derived, multi-index model of English spanning phonology, approximate phonology, lexical semantics, ontological relation, distributional association, and literary precedent — and an instrument that let a writer traverse all of it without leaving the page, and then I spent the back half of that decade trying to make the machine traverse it on its own. The dictionary was the surface that got published. The engine was the work.
---
[Bryant McGill](https://bryantmcgill.com/about/) is a Wall Street Journal and USA Today Best-Selling Author, founder of Simple Reminders, and architect of the Polyphonic Cognitive Ecosystem. He is a Congressionally Recognized Ambassador of Goodwill and a United Nations appointed Global Champion, whose work spans naval intelligence systems, computational linguistics, and civilizational governance architecture.
---
## References
- [WordNet — Princeton University](https://wordnet.princeton.edu/)
- [RFC 2229: A Dictionary Server Protocol](https://www.rfc-editor.org/info/rfc2229/)
- [Datamuse — origins of the Semantic Rhyming Dictionary and RhymeZone](https://www.datamuse.com/blog/)
- [Thinkmap Visual Thesaurus](https://www.visualthesaurus.com/aboutus/)
- [Roni Rosenfeld — publications on trigger-based language modeling](https://www.cs.cmu.edu/~roni/publication_list.html)
- [Switchboard-1 Release 2, Linguistic Data Consortium](https://catalog.ldc.upenn.edu/LDC97S62)
- [Broadcast News transcripts, Linguistic Data Consortium](https://catalog.ldc.upenn.edu/LDC99T36)
- [Wikipedia — Wikimedia Foundation](https://wikimediafoundation.org/)
- [DIContainers — Yunqa, The Delphi Inspiration](https://www.yunqa.de/delphi/products/containers/index)
- [DIContainers: Version History](https://www.yunqa.de/delphi/products/containers/history)
- Linux.com, "McGill English Dictionary of Rhyme," December 12, 2004
- Madalina Boboc, review of VersePerfect 2.0, Softpedia, 2008
- Joseph Pisano, review of VersePerfect, _MusTech.Net_, April 2007