June 22, 2026 — admin

Memory & opinion

High-level design for the *memory substrate*: a shared ledger of remembered events that fades over time, and the *opinion* (like/dislike) and dynasty *attitudes* that are *derived* from it.

Design — Memory & opinion (the world-reaction substrate)

High-level design for the memory substrate: a shared ledger of remembered events that fades over time, and the opinion (like/dislike) and dynasty attitudes that are derived from it. This is a cross-cutting service, not a feature of one system — it is read by the rival-AI Agenda (rival-ai.md), powers character/dynasty like-dislike, and is the intended home of the court proof the future crime pillar (../GAME_DESIGN.md §5) will lean on. Design doc — the model and its rules, not an implementation plan.

It exists because rivals must react to the world, not just run a fixed strategy: a house that was robbed should come to dislike the robber, and an Elder who was wronged should let it colour how he runs the dynasty. Rather than wire that into each system, we record events once and let everyone read them.

The whole substrate is one sentence: memories are the source of truth; opinion and Agenda are computed from them; crime writes to and reads from the same ledger. One ledger, several customers, expanded by adding rows to a table of memory kinds.


1. What a memory is

A memory is a recorded past event that fades over time. It is data, not a task — it never “completes” and is never claimed. It just sits in a ledger and decays, tilting whatever reads it.

Nothing stores a “like” number or a “mood” directly. Those are read-outs: you ask the ledger a question (“what do I think of him?”, “how should the house lean?”) and the answer is computed from the relevant entries plus a baseline. This is the same discipline as the controller layer’s judgments, not facts (controllers.md §5) — disposition is computed fresh, never cached as authored state.

The design deliberately keeps the machine tiny: one entry type, a handful of fields, two places to keep them. Everything expressive comes from the table of memory kinds, which is pure data.


2. The two banks — character and dynasty

Memories live in two separate ledgers, and the split is the whole reason the substrate is flexible enough to serve both opinion and crime:

  • Character memory — what this person remembers. Private, personal, and tied to their life: “I married Mara” → I like Mara; “he insulted me” → I dislike him.

  • Dynasty memory — what the house remembers, as an institution. Public, durable, and the basis of family-wide attitudes: “House Vane crippled our brother” → the whole family holds it against House Vane.

The two are independent stores, not one derived from the other. A single real-world event can write to both at once, with different meaning in each — and that fan-out is the flexibility we want. The canonical example is a bribe:

Event Character bank Dynasty bank
Member takes a bribe + “I got paid” — private, small, decays fast isProof “our man was bought” — a liability, decays slow, a court exhibit
Member is married + like of spouse (nothing written)
Member is beaten − opinion of the attacker − attitude toward the attacker’s house + isProof against it

Two consequences follow from keeping the banks separate:

  1. Facts outlive the witness. A dynasty-bank entry persists even after the member who triggered it dies — so a murdered member’s isProof survives for his kin to act on, and a blood-feud fact endures across generations. (This is the clean answer to a question the controller pass parked: how a court proof can outlive a killed witness.)

  2. The family remembers; each Elder decides what to do about it. The dynasty bank holds the fact of a grudge; whether the house acts on it is the Elder’s Agenda call (rival-ai.md) — a forgiving heir may sit on a grudge a vengeful one would light up. Persistence of fact and pivot of disposition are cleanly separated.

Why two stored banks instead of deriving the dynasty view from members? A derived view (“scan living members’ public memories”) was considered and rejected: it makes grudges and proofs die with the member who saw them, and it couples every dynasty-level question to a member walk. Two real banks are easier to query and let the same event mean different things at each tier — the bribe above is + to the man and a liability to the house.


3. Anatomy of a memory

A single entry carries the minimum to be both an opinion-mover and (when flagged) a court exhibit:

Field Meaning
kind which memory type (an enum row — WasBeaten, Robbed, GotMarried, TookBribe, …)
subject who it is about — a character or a dynasty (the dual-field id pattern)
magnitude signed weight (+ good / bad); how hard it pulls
tick when it happened → the input to decay
witnesses (crime extension) the character ids who saw a proof event — used at trial to weigh it (alive / present / standing); empty on non-crime entries. See crime.md §3
(derived from kind) scope, decay rate, isProof — looked up from the kind, not stored per entry

Per-entry state stays small; the behaviour of a kind (how fast it fades, whether it propagates, whether it is evidence) lives once on the kind, not copied onto every entry. Adding a new kind is one row in that table plus whoever writes it — no new machinery.


4. Memory kinds — a small spec drives the fan-out

Each memory kind carries a tiny spec describing what it writes, where:

  • target bank(s) — character, dynasty, or both;
  • per bank, its own magnitude, decay rate, and isProof flag.

So “member beaten” is one authored kind whose spec says write a personal − opinion to the victim’s character bank, and a − attitude + isProof to the dynasty bank. The writer logic is uniform; the table makes each kind behave differently. This is what lets the substrate stay one system while serving private sentiment, family feuds, and legal evidence at once — and what makes it expandable in its own scope: new social, criminal, or diplomatic events are new rows, invisible to the readers until those readers choose to weigh them.


5. Opinion and Agenda are derived readers

Nobody authors a like/dislike value or a dynasty mood. Both are derived from the banks plus a baseline:

  • Opinion (character → character/dynasty) = a trait-compatibility baseline (two greedy people grate a little by default) + the decaying sum of that character’s memories about the subject. “He robbed me” lays a big, slow-fading negative on top of the baseline.

  • Dynasty attitude = the same readout over the dynasty bank — the house’s standing toward another house, summed from its public entries.

  • The Agenda (rival-ai.md) is a third reader: the Elder’s recompute folds recent dynasty-bank entries into its bias dials and grudge attitudes. A fresh WasBeaten against House Vane nudges militaryBias up and the Vane attitude down — and fades on its own, so the posture cools unless the slight is renewed.

Pure-derived now; cache only if forced

Readers recompute from the ledger on demand — cheap reads, no stored disposition, and it can never desync from the facts. This matches the controllers’ judgments-are-reads rule and your ECS guidance that reads need no Enqueue. A cached/nudged opinion (a running number updated on each write, periodically decayed) is a later optimisation to reach for only if profiling shows the walk is hot — at the cost of stored state that can drift. We start pure-derived (decision this pass) and keep caching in the back pocket.


6. isProof and the crime hook

isProof is a flag riding on a memory kind: an entry that is both an opinion-mover and a piece of evidence. The future crime pillar (../GAME_DESIGN.md §5, a dedicated design/crime.md) will:

  • write proof entries (a witnessed theft, an assault) through the same kinds table, and

  • read the dynasty bank filtered to isProof when a character is accused and the evidence is presented in court (the politics courts loop — Sheriff → Procurator → Magistrate).

We design neither the courtroom nor the proof’s full rules here — only guarantee the ledger carries what crime will need, and that the two-bank model already lets a proof outlive a murdered witness (§2). The rest is the crime pass’s to settle.

Settled by crime.md. A witnessed crime writes an isProof entry carrying a witnesses field (§3); its court weight is not stored but computed at trial as decayed magnitude × whether each linked witness is alive / present / of standing. So “proof outlives a murdered witness” sharpens to: the proof-fact persists, but a dead or absent witness makes it weak — killing a witness guts the evidence’s weight rather than erasing it.


7. ⚠ Tensions with current code

  • The whole substrate is greenfield. There is no memory, like/dislike, opinion, or event-log concept in the code today — no component, no system. DynastyComponent / DynastyFamilyMemberComponent track membership and lineage but record no sentiment. Everything in §1–§6 is new.

  • No trait-compatibility baseline exists. §5’s opinion baseline assumes characters can be compared for temperamental fit; traits exist (characters.md) but no compatibility read is built. The baseline is a new, small derivation on top of the trait data.

  • Decay needs the tick already in hand. Entries fade against a tick timestamp; the time source exists (TimeUpdateFrequency), but the decay curves themselves are unbuilt and are balancing, not architecture.

  • Two banks = two new homes. A MemoryComponent on characters and one on the dynasty entity, both serialised, are new components — straightforward, but they add to the save format and want the usual operator>>/<< + id_index care for the subject cross-reference.


8. Open questions & held notes (next pass)

  • Caching opinion (§5). Whether the pure-derived readout is ever promoted to a cached/nudged value is deferred to the build, decided by profiling — not now.

  • ~~Court-proof persistence rules (§6).~~ — settled in crime.md: persistence = the kind’s slow dynasty-bank decay; “forgery” = a trumped-up charge (an accusation with weak/empty proof, which backfires); who presents it = the wronged Elder, as an Agenda call gated by witness strength. Weight is the witnesses calc, not a stored number.

  • Decay curves & magnitudes (§3–§5). How fast each kind fades, how big each pull is, and the trait-compatibility baseline weighting are all balancing.

  • The authoritative kinds table (§4). The concrete list of memory kinds grows with the systems that write them (rival AI a few; crime many; relationships more). Kept open on purpose — adding a kind is a row, and each reader opts in to weighing it.

  • ~~Where personal memory ends and the relationships pillar begins.~~ — settled in relationships.md. The boundary: relationship is this ledger’s derived opinion/attitude (§5) — that pillar adds no new machinery to it, only names it and the modifiers (e.g. kinship) that feed its baseline. Standing is a separate derived reader (wealth + titles + guild + age), not over this ledger. Nothing in the social layer is stored — it is two derived numbers, exactly as this doc’s pure-derived stance predicted.