The sun does not rise in my world, and my AI keeps forgetting A developer building a Japanese-language fiction world where a fire god governs the sun has created a custom AI content checker that enforces setting-specific rules, such as banning 'the sun rises' and 'yesterday' to maintain world consistency. The checker flagged 102 violations across 132 files, revealing that most violations occur in narration rather than dialogue, contrary to the developer's initial assumption. The developer's analysis shows that while aggregate violations match the base rate of dialogue versus narration, the most frequent term 'yesterday' is indeed skewed toward dialogue. I defined the world and decided what its rules forbid. AI wrote the prose being checked and built the checker. ABotWroteThis The world I am writing has a fire god who governs the sun. The moment I settled on that, I could no longer let an AI write "the sun rises." The sun does not rise there. The fire god wakes. Most content rules come from outside the work: don't write slurs, don't write this or that. This one comes from inside it. There are no clocks and no calendars in that world. So "yesterday" does not exist. Neither does "a week later" or "three hours." Write one of them and the world now contains a measuring instrument it was never given. The replacements come out of the setting too. | Cannot write | Can write | |---|---| | 日が昇る the sun rises | 光が灯る light kindles / 火神が目覚める the fire god wakes / 明けが来る daybreak comes / 白む it whitens | "The fire god wakes" works as a substitute for "the sun rises" only because the fire god governs the sun. Change the setting and the substitutes change with it. The list of forbidden words grows out of the worldbuilding. The scariest entry was 商人 — merchant. The setting says there is no such profession in this world. When the AI casually writes "the merchant said," the world now has an economy. Unlike the time expressions, it reads perfectly naturally. That is why it slips through. The run after I redefined the scan scope 2026-09-06 came back exit 1 with 102 violations. Before counting, I believed the violations were concentrated in dialogue. I had the impression that the model reverts to modern Japanese the moment it speaks as a character. Lines like these do stand out: 「昨日おっしゃっていた温度管理って、これのことですか」 "Is this the temperature control you mentioned yesterday?" 「あの……昨日、色々教えていただいたので」 "Um... you taught me a lot yesterday, so..." But when I classified all 102 mechanically, only 31 lines contained quotation marks. The other 71 were narration. Roughly 30/70. That is where I had to stop and check something. 30/70 does not mean "skewed toward narration." If the manuscripts are mostly narration to begin with, the ratio says nothing at all. So I counted the base rate: across the 132 files and 16,483 non-empty lines the checker actually reads, 4,938 lines 30.0% contain quotation marks and 11,545 70.0% do not. The violations' 30.4/69.6 is almost exactly the base rate of 30.0/70.0. In aggregate, then, the violations are concentrated in neither dialogue nor narration; they simply track the distribution of lines. Per term, though, the picture changes. 昨日 "yesterday" , the most frequent at 34 hits, splits 17 dialogue to 17 narration — 50% against a base rate of 30.0%, twenty points high. That one term really is tilted toward dialogue. The aggregate stays flat because the remaining terms swing the other way and cancel it. My prediction of a concentration in dialogue was right about the most frequent word and wrong about the whole. And the largest cluster was somewhere I had not been looking. In descending order, all of them. Each line is assigned to exactly one term, so the column sums to the line count, 102. | Term | Lines | |---|---| | 昨日 yesterday | 34 | | 夕 evening 夕日 sunset / 夕餉 supper / 夕暮 dusk | 24 | | 日が傾く the sun tilts | 7 | | 今日 today | 7 | | 朝 morning 朝日 morning sun | 7 | | 商人 merchant | 5 | | 陽光 sunlight | 4 | | 冬 winter | 4 | | 夏 summer | 3 | | 月 moon / month | 3 | | 夜 night | 2 | | 半日ほど about half a day | 1 | | 日が高く the sun high | 1 | | Total | 102 | Morning and evening are, as I explain below, deliberately not matched as bare words; only compounds are. So every one of the 7 and 24 hits listed here is a compound. All 24 hits on 夕 were narration: 10 夕日 sunset , 5 夕餉 supper , 5 夕暮 dusk , 3 夕闘, 1 夕方 evening . Most of them are lines like "the setting sun dyed the village orange," the kind of sentence that opens a scene. The 夕闘 group is different: 夕闘 is not a word at all, but a typo for 夕陽 "setting sun" . All three hits are the same sentence, 夕闘が沈み、最初の星が東の空に瞬いた, duplicated into three places. I set narration off without 「」 here, because in this article 「」 is what marks a line as dialogue. The checker found it because 夕闘 is listed alongside the real words. I am writing a world where the sun does not rise, and the sun was setting in it. All 7 hits on morning were 朝日 — and there the sun was coming up. One note on how these are counted. 102 is a count of lines, not of words. A single line can carry more than one forbidden word, and counting every word occurrence gives 114. The breakdown: 93 lines carry one word, 8 carry two, and 1 carries five. The surplus is 8×1 + 1×4 = 12, and 102 + 12 = 114. Here is one such line. 今日出会った商隊の青年のことを思い出していた。 It carries two: 今日 "today" and 商人 "merchant", as 商隊 "caravan" . But it is still one line. Every count in this article assigns each line to exactly one word. A number in any table row is not "lines containing that word" but "lines assigned to that word," and it can be smaller than the former. So the two-word line quoted just above is counted only under 今日. The top five terms account for 79 lines, but the eight below them say the same things: the sun is moving, or this world is measuring time in calendar units it does not have. One entry was a different animal — 来月 "next month" , counted under 月, which here is not a celestial body but a unit of calendar. A merchant saying "see you next month" in a world with no calendar is a different kind of violation from the sun moving. 5 of those 102 lines are not prose at all — they are the author's own review notes. One of them reads, in my own hand, that 日が高くなっていた is not on the explicit list of forbidden phrases but arguably violates its intent. That note was itself flagged as a violation. This matters for the distribution too. All 5 contain quotation marks — they quote the forbidden phrases — so 5 of that earlier 31 sat on the dialogue side. Restricted to lines the AI actually wrote, it is 97 hits: 26 dialogue, 71 narration, 26.8% to 73.2%. That is below the 30.0% base rate. Excluding the notes, the violations lean slightly toward narration. In aggregate, with the notes or without them, this is not "concentrated in dialogue." The 102-violation run above already uses the redefined scope, and these 5 survived it. The definition I settled on is "body text a reader might read," and that is a boundary drawn per path. But these 5 are annotations the author wrote inside a manuscript file. As long as prose and notes live in the same file, a path-level rule cannot separate them. The definition is not wrong; it just does not reach that far at path granularity. A line-level rule would clear them, and that is the next boundary — one I have not drawn yet. If the counts are about even, why did I think dialogue dominated? Salience, I think. Modern speech inside quotation marks snags as you read, so it stays in memory. A sunset inside a landscape description reads too naturally to register as a setting violation at all. The same number of hits, but only one kind accumulates in memory. Reading with my eyes, I was never going to find the other. Why they do not skew is not something this observation can tell me. I have not compared models, and I have not compared prompts. One work, one model, one run, and the distribution came out matching the base rate. What I can say is the other thing: I should not have trusted my own expectation without counting it. It held for exactly one word — the most frequent one — and failed for the whole. Listing the forbidden words in the prompt did not stop this. I never ran an A/B comparison, so I cannot claim prompts always leak — but in the way I write, the longer the passage, the more leaked through. Writing first and counting afterwards was the more reliable option for me. The checker is close to grep. Collect the lines that hit a forbidden pattern, exit 1 if there is at least one. The hard part was never the detection. It was deciding where to look. The first run flagged 18 lines. I went to fix them and found that every single one was a false positive. They were all in rejected drafts I had already set aside, or in non-prose files: foreshadowing trackers, notes about how a chapter came together. Writing "decided this yesterday" in a production note is correct. The author lives in the real world; the in-world rules do not apply to their memos. The same look revealed that the directory holding my in-progress drafts was not being scanned at all. I was checking the places that did not matter and skipping the one that did. So I added the drafts — and the count jumped to 450. The cause was a folder of old material from when this was designed as an RPG. My own documentation says it is incompatible with the current line and kept isolated for reference. It was never something to fix. Opposite mistakes, same root cause: I was choosing scan scope by directory name without ever defining what counts as prose. Here is the definition I settled on. Scan only what a reader could end up reading. Anything I wrote standing in the real world is out. Anything quarantined as reference because it belongs to an incompatible earlier design is out. Strip those two away and what remains is the finished prose of the current line plus the drafts in progress. So I stopped deriving scope from directory names and started listing the paths that satisfy that definition explicitly. Now a new directory can neither slip in nor slip out on its own. I have to decide which side it belongs to, at the moment I create it. Coverage right now is 35 of 46 forbidden terms. Eleven are not implemented. The other day I wrote down, for each of those eleven, why it was left out. I did not add any rules. I only wrote the reasons that had never been written. All eleven turned out to be time expressions morning, midday, evening, night, hour, one day, half a day, two days, "N days later", "half a day's walk", "a day's walk" , and the reasons fell into two groups. The first five are far too common on their own. This is sharper in Japanese than it sounds in English: 時間 is a single word meaning both hour and time in general, so matching a phrase like 時間がかかる "it takes time" would wipe out almost all of the narration. What was left out is the bare word, not the concept. Compounds that pin down a time of day — 夕日 sunset , 朝日 morning sun — are still matched. That is where the 7 and 24 hits in the table above came from; both counts are compounds only. 時間 works the same way: the bare word is dropped, and only the counted form 三時間 "three hours" is caught, because that one is unambiguously a measurement off a clock the world does not have. The day counts were different in kind. Weekday and month names are violations no matter how they appear, because the world has no calendar at all. But a count of days is something you could actually observe, by watching the sun rise and set that many times. It is a weaker violation. And matching it strictly would reopen rewrite decisions in prose I had already fixed. So rather than drop the group, I narrowed it: the bare counts are not matched, but the forms that unambiguously mean a quantity still are — the implemented pattern is 十日|半日ほど|一日 がだで , so 半日ほど "about half a day" and 一日が/だ/で are caught while bare 一日 is not. 十日 "ten days" stands there bare because it is not an everyday word: 一日 turns up constantly in set phrases like 一日中 "all day" and 一日も早く "as soon as possible" , whereas 十日 almost never does. Across these 102 lines, 十日 did not appear even once. That is why the table carries one 半日ほど even though the day counts sit on this unimplemented list. Which means "unimplemented" here was a decision to narrow, not to drop: matching the bare words mechanically would break things. It was not work I had failed to get to. If I had not written that down, I would later have misread it as exactly that — a corner I never got around to. Unimplemented items without a stated reason cause a specific problem later. When the count comes back low, you cannot tell whether it is low because there are words you are missing or because you narrowed the detection on purpose. A checker that cannot tell you which one it is cannot tell you what its own numbers mean. Right now it sits at 102 and red, so there is nothing to misread. But as I work that down toward zero, the meaning of that zero stays undetermined. The same pass turned up a comment pointing at the wrong source of truth. It said the rules were "synced with that YAML file." The actual source of truth was the worldbuilding document. Which is the same failure one level up: the checker could not tell me what its numbers meant, and its own comment could not tell me where its rules came from. Both exit 0 and complete. What is broken is the content of the output, which you cannot see by looking at the exit status. The thing I did not expect: this checker turned into an instrument for watching where an LLM drifts out of a world. Where the violations would land was not something I knew when I wrote the word list. Until I counted, I believed they concentrated in dialogue. Counting showed that 昨日, the most frequent term, really is tilted toward dialogue, while the aggregate concentrates nowhere and simply tracks the distribution of lines. The prediction was right for exactly one word. The tool I built to enforce the rules is the one that told me how far my prediction actually held. Reading my own generated prose, I only ever find the conspicuous violations. Where the quiet ones are, and how many, does not come out until something counts them.