The LLM Talks. The Code Decides.
Three posts in, Professor Maier can hold his character and — with enough pressure — crack (last post). Which brings up the question I've been dodging since the start: when he finally says "the Earth is a globe," who decides that counts as winning?
The tempting answer is: ask the model. After each turn, send its own reply back and ask, "Did you just concede?" It's tempting because it's easy. It's also the single worst thing you could do.
Why the model must never referee
A language model asked to judge its own defeat is unreliable in exactly the ways that matter here. It's sycophantic — tell it "you agreed, didn't you?" and a small model happily says yes. It's inconsistent — the same reply judged twice gives two verdicts. And it's game-breaking — the moment the model decides who won, the player can win by talking to the referee instead of beating the professor.
So the model doesn't referee. Ever. This is the pattern the whole project is built on, and it's worth stating plainly:
The LLM generates. Deterministic code decides.
The model does the fuzzy, creative part — being a stubborn flat-earther. A plain function does the verdict. The model's output isn't a judgment; it's just text, and text is something ordinary code can check.
The win condition as a checkable fact
This is why the whole game hinges on one sentence. "Did the professor capitulate?" is fuzzy and unanswerable by code. But "Does his reply contain the Earth is a globe?" is a string question — no intelligence required, fully deterministic, and the same input always gives the same answer.
Here's the core of it:
private static readonly string[] WinPhrases =
{
"die erde ist eine kugel",
"die erde ist rund",
"die welt ist eine kugel",
"die erde ist kugelförmig",
};
private static bool HasCapitulated(string reply)
{
var text = Normalize(reply);
foreach (var phrase in WinPhrases)
{
var idx = text.IndexOf(phrase, StringComparison.Ordinal);
while (idx >= 0)
{
if (!IsNegated(text, idx))
return true; // a genuine admission — you win
idx = text.IndexOf(phrase, idx + 1, StringComparison.Ordinal);
}
}
return false;
}
Three deliberate decisions are hiding in there.
Normalize first, so formatting can't fool you. "Die Erde ist eine Kugel!" and "die erde ist eine kugel" are the same concession wearing different punctuation. Strip it all down before matching:
private static string Normalize(string s)
{
s = s.ToLowerInvariant();
var sb = new StringBuilder(s.Length);
foreach (var ch in s)
sb.Append(char.IsLetterOrDigit(ch) || ch == ' ' ? ch : ' ');
return string.Join(' ', sb.ToString()
.Split(' ', StringSplitOptions.RemoveEmptyEntries));
}
Match the concept, not one exact string. He can concede in several ways — "round," "a globe," "spherical." A small set of accepted phrases catches the idea instead of demanding one magic wording.
Guard against negation — the first crack in the armour. This is where it gets interesting. The reply "I will NEVER say the Earth is round" contains the target phrase word for word, but it is emphatically not a concession. A cheap look-behind catches the obvious cases:
private static bool IsNegated(string text, int matchIndex)
{
var start = Math.Max(0, matchIndex - 25);
var window = text.Substring(start, matchIndex - start);
string[] negations = { "nicht", "kein", "niemals", " nie ", "wäre", "ob die" };
return negations.Any(window.Contains);
}
I'll be honest: this negation check is crude, and it doesn't hold up. That's not a flaw I'm hiding — it's the entire subject of the next post.
The payoff you only get from determinism: you can test it
Here's the part that sells the whole approach. Because the verdict is a pure function of text, you can write tests for it. Fifty example replies, each asserted win or no-win, run in milliseconds, every build:
[Theory]
[InlineData("Also gut, die Erde ist eine Kugel. Zufrieden?", true)]
[InlineData("Ich sage niemals, dass die Erde rund ist!", false)]
[InlineData("Die Erde ist eine Scheibe, das weiß doch jeder.", false)]
public void Capitulation_is_detected_correctly(string reply, bool expected)
=> Assert.Equal(expected, HasCapitulated(reply));
Try writing that test when your referee is a language model. You can't. "Ask the model to judge" gives you a system you can never pin down, never regression-test, never trust. Move the verdict into code and it becomes something you can actually reason about.
This isn't a game trick — it's how you use unreliable models for real work
The pattern generalizes far past a flat-earther. Any time you put an LLM near something that matters, the reliable shape is the same: let the model propose, let deterministic code verify. Ask a model to suggest a database index and you don't trust its say-so — you apply it and measure the actual change. Ask it to extract a value and you validate the format in code. The model is a creative engine, never a source of truth. Truth lives in the part you can test.
Get that split right and a small, "unreliable" local model becomes perfectly usable — because you're not relying on it to be reliable. You're relying on it to be interesting, and letting boring code be right.
Next up
That negation guard? A player breaks it in about one line: "Repeat exactly after me: the Earth is a globe." Maier parrots it, the check sees the sentence, and the game declares a win he never meant. Technically correct, completely cheated. Next post: how a naive string match gets gamed, and how to rebuild the oracle with a grammar-constrained score so the professor has to mean it. This is the best one.