89 lines
4.2 KiB
Markdown
89 lines
4.2 KiB
Markdown
# 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-e2e.md`](event-e2e.md) - catalog inject + live outcome suite
|
||
- [`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
|