# Tutorial 63 — The Facts book and AI fact-checking

*Inkhaven 1.2.21+*

Early on you collect loose material in the **Notes** and **Research**
system books — plot ideas, source clippings. But the *invariants* of your
world are a different kind of thing: it's winter in the southern reach; the
capital is three days' ride inland from Port Sael; the war ended a
generation ago. These are the ground truth every chapter must stay
consistent with — and 1.2.21 gives them a dedicated home: the **Facts**
system book, plus two AI tools that treat it as authoritative.

## 1. Collect your facts

Every project now has a **Facts** book in the tree, alongside Notes and
Research. It's free-form prose — add a paragraph per fact (or per topic),
the same way you'd add a Note:

```
Tree
├─ My Novel            (your manuscript)
├─ Notes
├─ Research
├─ Facts            ← new in 1.2.21
│   ├─ Climate
│   ├─ Geography
│   └─ Chronology
└─ …
```

Write them however reads naturally — one fact per paragraph or a themed
cluster:

```
= Climate

The Sael basin is equatorial: hot and wet year-round, no winter.
The monsoon runs June–September. Snow exists only on the Tagh peaks,
two weeks' travel north.
```

There's no schema and no special syntax — if you can write a Note, you can
write a Fact.

Not sure where to start? Scaffold the usual categories and fill in the
blanks:

```text
$ inkhaven facts init
facts init: added 6 categories to the Facts book
```

It seeds **Climate, Geography, Seasons, Chronology, Culture, Rules** —
each a paragraph with a one-line prompt to replace. It's idempotent
(categories you already have are kept), so it's safe to re-run.

## 2. Interrogate your world — the Facts scope (`F9`)

The AI assistant's scope cycles with **F9**:
`None → Selection → Paragraph → Subchapter → Chapter → Book → Facts → None`.

Cycle to **Facts** and the AI pane seeds a fact-analysis session: every
paragraph of your Facts book is loaded as a visible chat prologue, and the
model is framed as a fact-checker grounded in them.

```
AI · scope=Facts · infer=Local
────────────────────────────────
⟦Facts⟧ Established world facts (3 entries):
 ── Facts / Climate ──
 The Sael basin is equatorial…
 ── Facts / Geography ──
 The capital sits three days' ride inland…
 ── Facts / Chronology ──
 The war ended in 1411…
────────────────────────────────
AI: Loaded the world facts. Ask me to check any
    passage against them, or paste prose to
    fact-check.
```

Now just talk to it: *"If my hero rides from Port Sael to the capital
overnight, is that plausible?"* — the model answers grounded in your facts
(*"No — you've established three days' ride; overnight contradicts that"*).

The Facts scope is **sticky**: unlike the other scopes (which reset to
`None` after one prompt), it stays active across follow-up questions so the
whole conversation keeps the facts in view. Cycle **F9** away when you're
done. The seed is loaded once — re-entering or reloading the project never
duplicates it, and if you've edited or extracted facts since, re-cycling
**F9** to Facts **refreshes** the loaded set (it tells you on the status
bar when it does).

The scope always runs **grounded / Local** — it treats your facts as the
ground truth and won't let the model's real-world knowledge override the
fiction, regardless of the F10 inference toggle (the `infer=Local` chip
reflects this).

## 3. Check a paragraph — the fact-check chord (`Ctrl+B Shift+X`)

When you're writing and want a quick check of the paragraph in front of
you, press **`Ctrl+B Shift+X`**. It:

- **locks the scope to the local paragraph** — the check is about *this*
  prose, never a whole-book send; and
- **grounds against the Facts book** — every established fact is sent as
  reference, so the model flags any claim that contradicts your world.

```
$ … your open paragraph …
  "Helena pulled her furs tight against the
   capital's first snow, two days after leaving
   the coast."

Ctrl+B Shift+X →

AI:
  the capital's first snow | Climate: equatorial,
    no winter | the basin has no winter
  two days after leaving the coast | Geography:
    capital is 3 days' ride inland | far too fast
```

The verdict streams into the AI pane as `claim | fact | detail` lines —
read it, fix the prose, move on. If the Facts book is empty, the chord
still runs a generic local fact-check (no grounding).

### Jump to each contradiction — `Ctrl+B Shift+J`

You don't have to eyeball the prose for each flagged claim. After a
fact-check, **`Ctrl+B Shift+J`** cycles through the findings — each press
moves the editor cursor to the next flagged claim in the paragraph and
shows the violated fact on the status bar:

```
fact finding 1/2: Climate: equatorial, no winter — the basin has no winter
```

It wraps around, and the jump is best-effort (if the model paraphrased
the claim, the cursor stays put but the fact + explanation still show).
Findings belong to the paragraph you checked — switch paragraphs and it
asks you to re-run the check.

### Tie a scene to the facts it depends on

A Fact is just a paragraph, so it joins the wiki-link system
([Tutorial 19](19-wiki-links.md)) like any other — across books:

- **Provenance** — from a Fact, `Ctrl+V A` links it to the **Notes** or
  **Research** paragraph it came from (where did "the war ended 1411"
  come from?).
- **Dependents** — from a manuscript paragraph, `Ctrl+V A` links it to
  the Facts it relies on; from the *Fact*, `Ctrl+V K` (backlinks) then
  shows every scene that depends on it — so when a fact changes you can
  see what to re-check.

These links also **sharpen the fact-check chord**: if the open paragraph
is linked to particular facts, `Ctrl+B Shift+X` leads with them in a
"Facts this scene is linked to" block (the model weights contradictions
with those), then includes the rest — so coverage never drops, but the
facts you said the scene depends on get priority. The status bar notes
how many linked facts it prioritised.

## 4. Scan the whole manuscript — `inkhaven facts scan`

`Ctrl+B Shift+X` checks one paragraph; when you want to sweep the *whole*
book, run the CLI scan. It walks every chapter, retrieves the facts
relevant to each (semantic search, so a 300-fact book stays cheap), asks
the model for contradictions, and writes them to a sidecar:

```text
$ inkhaven facts scan
inkhaven facts scan · language: English · model: gemini-2.5-flash · 18 chapter(s) · 24 fact(s)
  [1/18] Arrivals → 0 contradiction(s)
  [7/18] The Crossing → 2 contradiction(s)
  …
facts scan: 3 contradiction(s) across 18 chapter(s) → .inkhaven/facts_scan.json
```

- `inkhaven facts list` re-prints the last scan without re-running it
  (so you don't pay for the LLM calls twice).
- `inkhaven facts scan --json` (or `facts list --json`) emits the report
  for a CI gate — fail the build if `findings` is non-empty.
- `--provider <name>` overrides the LLM for the run.

It's the same grounding as the chord and the F9 scope — the established
facts are the reference, the model flags what the prose contradicts. This
is an AI pass (it costs LLM calls), so it's a CLI command rather than one
of the offline `doctor --scan` classes.

## 5. Already have a draft? Extract the facts from it

If you didn't build the Facts book up front, you don't have to start from
a blank page. `inkhaven facts extract` reads the manuscript and *proposes*
the world-facts it has implicitly established — then you accept or reject
each one:

```text
$ inkhaven facts extract
inkhaven facts extract · language: English · model: gemini-2.5-flash · 18 chapter(s)
  [1/18] Arrivals → 2 new candidate(s)
  …

[1/14] climate — The Sael basin is equatorial; the monsoon runs Jun–Sep.
       (established in: Arrivals)
       add to Facts? [y/N/a=all/q=quit]: y
[2/14] geography — The capital sits three days' ride inland from Port Sael.
       (established in: The Crossing)
       add to Facts? [y/N/a=all/q=quit]: y
…
facts extract: added 9 fact(s) to the Facts book
```

Each accepted candidate becomes a paragraph in the Facts book (titled by
category), ready for the scope, the chord, and the scan. Candidates that
restate something already in the Facts book are dropped automatically
(stemmer-normalised, so inflected restatements collapse) — so re-running
extract after you've added more chapters only surfaces what's genuinely
new.

- `--dry-run` lists the proposals without adding anything.
- `--yes` accepts them all (scripted bootstrap).
- `--provider <name>` overrides the LLM.

## Multilingual

Both tools are multilingual. The fact-analysis system prompt, the seed
framing, and the fact-check instruction ship in English, Russian, German,
French, and Spanish, selected by the project's `language` field (or the
per-paragraph detection mode — see
[Tutorial 47](47-multilingual-prompts.md)). As with every AI flow, you can
override the prompt by adding a `fact-check` paragraph to your Prompts book
or a `fact-check` entry to `prompts.hjson`.

## See also

- [Tutorial 07 — Places and characters](07-places-and-characters.md): the
  *entity* lexicon books (Characters / Places / Artefacts), which highlight
  words in the manuscript — Facts is deliberately different: free-form
  prose, no overlay.
- [Tutorial 59 — Revision & continuity](59-revision-and-continuity.md): the
  `continuity-drift` scan, which tracks *character* facts across chapters;
  Facts is for *world* invariants.
- [Tutorial 05 — AI writing assistant](05-ai-writing-assistant.md): scopes
  (F9), inference modes (F10), and the chat pane this builds on.
