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:
-
Facts outlive the witness. A dynasty-bank entry persists even after the member who triggered it dies — so a murdered member’s
isProofsurvives 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.) -
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
isProofflag.
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 freshWasBeatenagainst House Vane nudgesmilitaryBiasup 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
isProofwhen 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 anisProofentry carrying awitnessesfield (§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/DynastyFamilyMemberComponenttrack 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
ticktimestamp; the time source exists (TimeUpdateFrequency), but the decay curves themselves are unbuilt and are balancing, not architecture. -
Two banks = two new homes. A
MemoryComponenton characters and one on the dynasty entity, both serialised, are new components — straightforward, but they add to the save format and want the usualoperator>>/<<+id_indexcare for thesubjectcross-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.