One Interface, Player and NPC
The instinct here is a PlayerState and a separate NPCState, with NPC wrongness tracked as a list of flagged mistakes. This project ruled that out directly. An NPC's error isn't metadata attached to a scholar. It's just claims, in the identical container the player's beliefs live in, that happen to diverge from ground truth.
The rejected shape, and what it cost
MinimalScholar's own type comment records what it replaced: NPC errors used to live in a corpusErrors field, a list of wrong-answer flags sitting beside the scholar's other data. That design works, in the narrow sense that it compiles and renders. What it can't do is let an NPC's mistake behave like a mistake: it can't be argued with, cited approvingly by a different NPC who happens to share it, revised when new evidence surfaces, or contradicted by the player's own later find. It's a label, not a belief.
The shipped design deletes that field. NPC wrongness is now simply claims inside an InterpretiveModel, the same container type the player uses, that happen not to match occluded ground truth. Nothing distinguishes an NPC's wrong claim from a right one at the type level, because nothing should: the engine that will eventually surface the contradiction has to reach it the same way it reaches every other claim, or the whole mechanic of one agent's error propagating into another agent's belief has nowhere to attach.
What the interface actually holds
InterpretiveModel carries five kinds of claim (about cultures, about specific artefacts, about chronological ordering, about other agents, plus a methodological stance shaping how new evidence gets weighed) and two kinds of bookkeeping (per-hypothesis strain, a queue of open contradictions). Every field is agent-generic: there is no isPlayer flag anywhere in the shape, and no field that only makes sense for one kind of agent.
interface InterpretiveModel {
agentId: string;
culturalClaims: Map<string, CulturalClaim>;
artefactClaims: Map<string, ArtefactClaim>;
chronologicalClaims: Map<string, ChronoClaim>;
agentAssessments: Map<string, AgentAssessment>;
methodologicalWeights: MethodologicalProfile;
strainScores: Map<string, HypothesisStrain>;
contradictionQueue: ContradictionQueue;
}
The type's own JSDoc states the consequence in one sentence: engine functions accept an InterpretiveModel as a parameter and never know whose model they are processing. Only the UI and store layers are allowed to treat the player as special, because a UI has to render a player's screen differently from an NPC's dossier. The lens calculator, the contradiction detector and the eventual peer-review generator all take the same shape either way.
What that constraint buys
Consider what has to be true for one specific mechanic to work: an NPC scholar publishes a confident, wrong claim about a culture's material preferences. The player reads that publication, forms a belief influenced by it, and their own later interpretation of a new artefact gets steered by a mistake that was never theirs. For that chain to run without a special case anywhere in the code, the NPC's wrong claim has to be a first-class citizen of exactly the same type the player's beliefs are made of, reachable by exactly the same contradiction machinery, capable of being cited, inherited, and eventually revised or retracted the same way. A corpusErrors flag never could have supported that: a flag doesn't have a confidence level, doesn't sit in an evidence chain, doesn't have a status that can move from active to challenged. A claim does.
This is also what makes peer review possible as a mechanic rather than a scripted event: reviewing another scholar's paper is comparing one InterpretiveModel's claims against another's, and the reviewer's own position is a defended stance built from the same materials as the paper under review, not an oracle's verdict. A hostile reviewer who happens to agree with you is stronger evidence than a friendly one, precisely because both are claims of the same weight and kind.
culturalClaims, methodologicalWeights and a subset of artefactClaims, while an NPC's agentAssessments and contradictionQueue are left empty. The difference is entirely in how the container gets filled and when, not in what shape it has to be to be read.
docs/11-deferred-design-questions.md §2.6, docs/06-knowledge-contradiction-model.md §6 · Types: src/lib/types/interpretation.ts (InterpretiveModel), src/lib/types/scholars.ts (MinimalScholar, the retired corpusErrors note)