worldbox-observer-mod/docs/event-observation.md

56 lines
2.7 KiB
Markdown

# 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.