# Event observation (observe → confirm → emit) IdleSpectator learns about the game through Harmony sensors. Those sensors must not treat AI noise as catalog truth. ## Pipeline ```text Patch (Observe) → EventOutcome (Confirm) → Feed Emit (EventReason + catalog) ``` | Layer | Responsibility | Lives in | |-------|----------------|----------| | Observe | Thin Harmony Prefix/Postfix; snapshot before when needed | `Events/Patches/*` | | Confirm | Did the semantic claim happen? | `Events/EventOutcome.cs` | | Bridge | Drop unconfirmed with `InterestDropLog`; else run emit | `Events/EventObservation.cs` | | Emit | Catalog dials, keys, `EventReason` labels | `Events/Feeds/*` | ## Rules 1. **Never** treat `BehResult.Continue` as success by itself. 2. Prefer **state setters** when they exist (`setLover`, `setParent*`, `newFamily`, `setFamily`, `addNewStatusEffect`). 3. Beh hooks need **before/after** confirmation (`EventOutcome.GainedFamily`, `StillSeekingLover`, …). 4. Unconfirmed attempts log `outcome_unconfirmed` via `InterestDropLog` (not silent). 5. New catalog events must name their confirm predicate (or setter) in the audit TSV `game_call_site` / `fix_action`. ## Example (family join) Vanilla `BehFamilyGroupJoin` returns Continue even when no nearby family exists. Confirm with `GainedFamily(before, actor)` before Emit `family_group_join`. Negative harness: Beh Continue with no family gain must log `outcome_unconfirmed` and must not register `rel:family_group_join`. ## Adding an event For every new camera-worthy catalog id, record three things up front: | Field | Meaning | Example | |-------|---------|---------| | **Confirm predicate** | `EventOutcome` helper, or “setter is the transition” | `GainedFamily`, `setLover(null)` | | **Fire API** | Vanilla call the live harness will invoke | `BehFamilyGroupLeave.execute`, `Actor.addTrait` | | **Expected live level** | Honest target after the runner claims | `id`, `id_drop`, `pipeline` (`shared=`…), or `unsupported` | Then implement: 1. Find the real game call site (setter or Beh). 2. Add/extend an `EventOutcome` predicate if Beh Continue is ambiguous. 3. Patch calls `EventObservation.Confirm` / `ConfirmAfter` then the domain feed. 4. Mark the audit row with the confirm rule (`event-trigger-audit.tsv` = design/call-site only). 5. Harness: live path asserts key **or** authored drop; assert absence / `outcome_unconfirmed` when the outcome fails. 6. Do **not** call `domain_feed` / inject and label it live. 7. Live proof lands in `.harness/event-live-coverage.tsv` via `EventLiveCoverageLedger.Claim` - that file is the source of truth for live levels, not the audit TSV. See [`event-live-verification-plan.md`](event-live-verification-plan.md) for claiming rules and suite wiring.