Everyone throws an LLM at novel-to-galgame. We wrote a rule compiler.
The open-source state of the art fine-tunes an 8B model on 669 novels and still misattributes roughly one line in eight. Our web tool goes the other way: a deterministic rule compiler — five attribution rules plus a standing right to abstain — that found 16 characters with zero false names in a Dream of the Red Chamber excerpt, and turns a 5MB, 258-chapter book into a stage report in 264 milliseconds. No model, no upload, works offline.

The prevailing answer to “turn a novel into a visual novel” is to burn LLM calls. The best-known open-source pipeline, novel2galgame, fine-tunes an 8B model on 669 Chinese webnovels, chains nine agents through an offline pipeline, and emits a brand-new game project. Its published scorecard: 86.7% speaker-attribution accuracy, and an F1 of just 30.5% on pure-text scene segmentation. That 86.7% sounds respectable until you do the arithmetic — on a stage, it means a wrong name plaque roughly every eight lines of dialogue.
We just shipped Story Theater: drop a .txt on the page and chapter one starts performing as a visual novel, name plaques attached. The whole pipeline makes zero AI calls — no model, no upload, and it keeps working if you cut the network after load. This post is about the attribution compiler underneath, and why we bet rules over probabilities for this particular job.
Four passes, all deterministic
Dropping a file triggers four passes: decode the text and find chapter boundaries; bootstrap a cast list from nothing but the prose; attribute every quoted line to a speaker; assemble scenes and a line sequence for the stage. Every pass is a pure rule system. Drop the same book ten times and the output is byte-identical — a sentence no LLM pipeline gets to say.
Where the cast list comes from
The web tool has no character cards and no configuration. The compiler doesn’t know who is in the book, so the list must bootstrap from text. The method is almost stubbornly narrow: count only the subject position of speech clauses. Holmes said earns Holmes one tally; word frequency, capitalization, honorifics — none of it counts. A name needs at least two tallies to make the list, capped at sixteen. One hit could be a coincidence; we would rather miss a minor character than invent one.
Guards sit on every step of the counting. In the Chinese pattern 对秦朝说道 (“said to Qin Chao”), Qin Chao is the listener — subject runs opening with an addressee preposition are discarded whole. In 搂了宝玉道 (“hugged Baoyu and said”), Baoyu is the one being hugged, not the one talking; object markers force an abstain. Causatives flip the logic: in让林夏说 (“let Lin Xia speak”), Lin Xia really is the speaker. Classical prose adds a peeling step: the run behind 贾母笑道 is literally “Grandmother Jia laughs” — the trailing action verb has to be stripped to recover the bare name.
Pressure-tested on a public-domain excerpt of Dream of the Red Chamber — three chapters, 20,000 characters, 243 quotes — the statistics found 16 cast members, all real names, zero noise, and merged “Daiyu” with “Lin Daiyu” into one person. Top three by tally: Baoyu at 36, Hongyu at 22, Daiyu at 21. Anything wrong is hand-editable on the report page, and the stage re-compiles the moment the list changes.
Five rules, first hit wins
With a cast in hand, each quote gets its owner. Rules fire in a fixed order; if all five miss, the line abstains.
Rule one, the leading speech clause: name plus speech verb, running straight into the quote —Elara whispered: “Go.” The constraints are tight: the verb must sit hard against the name (at most a two-character adverb gap in Chinese), and only punctuation may stand between verb and quote. Loosen either and false verbs start matching.
Rule two, carried-over subjects, the default mode of classical Chinese prose: “Baoyu walked over, sat down beside Daiyu … and asked:” The lead-in must start with a name, end with a speech verb, run into the quote through a colon, and be the paragraph’s only such opener before the line goes to Baoyu.
Rule three, the trailing clause: “Not tonight.” Qin Chao replied. The nastiest trap in the whole compiler lives here. In 林夏说。“嗯。” — “Lin Xia said.” followed by a bare “Mm.” — the full stop means “Lin Xia said” closes the previous line, and the “Mm” belongs to someone else. A colon would announce the next line instead. One punctuation mark decides ownership, and the compiler carries a dedicated ruling for it. English books get an inversion form: “…,” said Holmes coldly. Named subjects only — said he abstains.
Rule four, action-cued weak attribution: He set down the cup. “Let’s go.” No speech verb, yet every human reader knows. The rule learns only a sliver of that instinct: the previous sentence must open with a name, contain one of a short list of physical actions, and the paragraph must contain exactly one cast member. The action list stays short on purpose.
Rule five, alternation: once two characters fall into rhythm, authors stop tagging lines. Four gates must all pass before we infer — the first two quotes both carry hard evidence, they belong to two different people, the paragraph holds at most two cast members, and there are at least three quotes. Miss one gate and the whole paragraph declines to guess. Too many alternation runs die to a third voice cutting in.
Abstention is the foundation, not the apology
When all five rules miss, the quote plays as narration: original words untouched, just no plaque. This is the compiler’s foundation because the two failure modes are not symmetric. A missing plaque leaves the reader inferring from context — the paper-book default. A wrong plaque puts A’s words in B’s mouth, breaks the scene on the spot, and poisons trust in every plaque after it.
The trade-off was validated on a real book: 15 dialogue-dense passages from a three-million-character commercial novel, hand-annotated as gold truth — zero misattributions on strong attributions. The “said to Qin Chao” listener trap came out of that annotation round, not out of anyone’s imagination. The web port is locked to the app’s compiler by 47 self-tests, both on rule set version 9.
The same rules score very differently across styles, and the page says so. The Red Chamber excerpt attributes 65% of quotes (151 plaques, 80 abstains). Sherlock Holmes manages just 10% — Watson’s first-person retelling is wall-to-wall said I and said he, and pronouns abstain by rule. Ten percent looks bad on a dashboard. Every plaque it does hang is defensible.
The side effect of zero AI: speed
With no model in the loop, the ceiling is parsing itself. We built a 5MB synthetic book to push it — 258 chapters, 1.74 million characters, 20,898 quotes. Drop-to-report: 264 milliseconds, zero long tasks on the main thread. Jumping to chapter 200 lands in 106 milliseconds. Compilation is lazy — a chapter compiles when you enter it — so book size never touches per-chapter cost.
One honest note on scene segmentation: we don’t infer scene boundaries from prose at all — that is precisely the problem the fine-tuned pipeline scores 30.5% F1 on. We cut only at structural anchors like chapter headings, and say so in the report when a book has none. Routing around a hard problem is also an engineering decision.
Try it on your own book
The tool lives at /labs/story-theater, with the Red Chamber excerpt and a Sherlock Holmes story built in as public-domain demos. Your own files never upload. The full form — portraits, backdrops, picking a branch and letting AI continue the story from that fork — lives in the Foreverse app’s theater mode, running the same compiler.
What’s next is coverage: 65% still leaves a third of the lines on the narrator’s bench. Rules will keep landing, each one gated on the real-book gold set first. Fewer plaques is acceptable. A wrong one is not.
FAQ
Why not use an LLM for speaker attribution?
Two reasons. Error quality: the best open-source attribution model scores 86.7%, which on a stage means a wrong name plaque roughly every eight lines — and a wrong plaque hurts far more than a missing one. Engineering quality: a rule compiler costs nothing per run, works offline, and gives byte-identical output every time. The price is coverage — uncertain lines fall back to narration — and we take that trade.
Is a 65% attribution rate acceptable?
That's the measured rate on our Dream of the Red Chamber excerpt: 151 of 243 quotes got a name plaque, 80 abstained into narration. Abstained lines keep every word of the original text — readers infer the speaker from context, which is exactly what paper books have always asked of them. Meanwhile every plaque that does appear passed structural-evidence checks validated against a hand-annotated commercial novel: zero misattributions on strong attributions.
What if the cast list is wrong or incomplete?
The report page is editable: delete a name that slipped in, add a character the statistics missed, and the performance re-compiles instantly against the new list. The discovery layer is deliberately conservative — a name must appear at least twice in the subject position of a speech clause to qualify — so minor characters can be missed, but what makes the list tends to be real.
How does the web tool relate to the theater inside the Foreverse app?
Same compiler, same rule set (currently version 9), with 47 self-tests locking the web port's behavior case-by-case against the app. The difference is scope: the web version is a bare stage — zero AI, performing only the words already in your book. The in-app theater is the full form: portraits, generated backdrops, and the ability to pick a branch at a fork and have AI continue the story from there.
Questions or ideas? Join our Discord →