# Event reasons (camera + orange dossier) ## A/B layers | Layer | What | Gate | |-------|------|------| | **B ticker** | Activity / Life / chronicle prose for live ids | Always may write when the game fires | | **A story camera** | Interest candidates, orange reason, camera cuts | `EventCatalog.IsCameraWorthy` only | Happiness: `Presentation.Signal` = A; `Ambient` / `Aggregate` = B-only. Discrete / status / WorldLog: `CreatesInterest` = A gate. `InterestDirector` only ranks A candidates. It does not author inventory. Character fill runs only when the pending **EventLed** queue is empty, and uses an empty Label (task chip only). Orange dossier reason = the owning A event’s `Label` while the scene is active (`InterestDirector.TryGetOwnedReasonCandidate`). Quiet grace / inactive dwell clears the reason even if focus has not switched yet. ## Activity History peek freshness Layer B ring stays append-only (Lore Activity = full journal). Dossier History peek uses `ActivityRelevance` / `ActivityLog.LatestRelevantForSubject`: - Soft window (~8s): newest beats always show. - After that: same verb/family as live task, or family live predicates (combat ↔ attack target / in_combat, haul ↔ carrying, status ↔ still has/lost status). - Milestones always peek-worthy. - Peek refreshes when live AI fingerprint changes (task, attack target, carrying), not only when the ring grows. Drops (ambient, createsInterest=false, identity filter, below margin, …) go to `InterestDropLog`. ## EventReason [`IdleSpectator/Events/EventReason.cs`](../IdleSpectator/Events/EventReason.cs) is the sole Label factory for camera candidates. It lives in the Events module with catalogs (not in the director). Feeds call typed helpers (`Fight`, `BuildingEat`, `SeekingLover`, …) or `Apply(template, a, b, id)`. ## Events module layout | Path | Role | |------|------| | `Events/EventCatalog.cs` (+ `Catalogs/EventCatalog.*.cs`) | **One inventory** - nested domains + `IsCameraWorthy` | | `Events/Feeds/` | Build + register **A** candidates from catalog dials | | `Events/Patches/` | Harmony hooks → feeds / Activity | | `Events/EventReason.cs` | Orange reason sentences | | `Events/EventFeedUtil.cs` | Shared Register path | | `Events/InterestDropLog.cs` | Why a beat stayed B or missed A | | `Events/DiscreteEventEntry.cs` | Shared catalog row shape | Rules: - Sentence form with names: `{a} is fighting {b}`, `{a} is eating a beehive`, `{a} decides to change the kingdom's culture`. Decision orange reasons use the `action` field (infinitive after "decides to"). Other domains use `label` or happiness `variantsWithRelated` / `variantsWithoutRelated`. All of that - plus per-id `strength` - lives in [`event-catalog.json`](../IdleSpectator/event-catalog.json). `EventReason.Decision` only formats `{name} decides to {phrase}`. Species/character score overlays live in the same JSON; global formula stays in `scoring-model.json`. - Set `RelatedUnit` when the sentence names a second party. - Never invent stranger subjects for unit-led events. - Never use engine jargon that misstates the action. ## Removed crumbs Do not author these as orange reasons: - `In action` - bare `Duel` / `Fight (N)` without a named sentence - identity titles (`King of X`, `Leader of X`, `Lone beetle`) - `Consumes a building` / `Damages a building: {a}` crumb form ## Strength dial (Layer A only) `CreatesInterest` / Signal gates whether an id registers for the camera; `EventStrength` ranks among A. Instant cut-in when `next.TotalScore >= current + cutInMargin` (sticky scenes use `stickyCutInMargin`). See `scoring-model.json`. ## Dossier display `UnitDossier.EventLabelBeat` keeps EventReason / `Name verb …` sentences. It rejects identity/weak crumbs and scrubs living stranger names (`ReasonNamesStranger` / `LeadsWithForeignName`). Active EventLed A scenes should not silently blank a valid Label. ## See also - [`event-observation.md`](event-observation.md) - observe → confirm → emit (Beh Continue is not success) - [`event-audit.md`](event-audit.md) - sources, MaxWatch, A demotion set - [`event-prose-audit.md`](event-prose-audit.md) - prose vs game call sites - [`scoring-model.md`](scoring-model.md) - score weights