# Round Events and Projections

## Purpose
This guide covers the minimum steps to add a new host-auth round event and expose it safely to UI.

## Current architecture
- Event source of truth: `Code/Game/RoundEvents/RoundEventStoreSystem.cs`
- Event type enum + shared contracts: `Code/Game/RoundEvents/RoundEventTypes.cs`
- Typed payloads: `Code/Game/RoundEvents/Payloads/*`
- Projection dispatch: `Code/Game/RoundEvents/RoundProjectionRegistry.cs`
- Host projection implementations: `Code/Game/RoundEvents/Projections/*` and `Code/Game/RoundEvents/Services/*`

## Adding a new event type
1. Add a `RoundEventType` enum member in `Code/Game/RoundEvents/RoundEventTypes.cs`.
2. Add a typed payload class in `Code/Game/RoundEvents/Payloads/` implementing `IRoundEventPayload`.
3. Emit the event from a host-only service (or host game flow) with `RoundEventStoreSystem.AppendHost(...)`.
4. Set `RoundEventVisibility` and `AttributionConfidence` intentionally. Do not default to private/public without policy review.
5. If UI needs it, update or add a projection implementing `IEventProjection`.
6. Ensure the projection is created in `GameMode.SetupGame()` on host.

## Emission pattern (host only)
```csharp
if ( !Networking.IsHost )
    return;

var store = RoundEventStoreSystem.GetInstance( Scene );
if ( store is null || !store.IsRoundActive )
    return;

store.AppendHost(
    RoundEventType.YourNewType,
    new YourNewPayload
    {
        // fill required fields
    },
    RoundEventVisibility.Public,
    AttributionConfidence.Exact );
```

## Projection pattern
- Keep projections append-only and deterministic.
- Projection output should be a small replicated snapshot (usually JSON string fields already used in this repo).
- Projections should not mutate gameplay authority state.

Minimal skeleton:
```csharp
public sealed class YourProjection : SingletonComponent<YourProjection>, IEventProjection
{
    public void ResetForRound( int roundNumber, string reason ) { }

    public void OnEventAppended( RoundEventRecord roundEvent )
    {
        if ( !Networking.IsHost )
            return;

        if ( roundEvent?.Envelope?.EventType != RoundEventType.YourNewType )
            return;

        // update projection snapshot
    }

    public void SealRound( int roundNumber, string reason ) { }
}
```

## UI consumption rules
- UI should read from projections, not from gameplay-side mutation paths.
- Keep request/response flows host-approved (`[Rpc.Host]`) when UI triggers data fetches.
- Never let client UI write round authority state directly.

## Checklist before merge
- Event is appended only on host.
- Event has a typed payload (no stringly ad-hoc blobs).
- Visibility is correct for secrecy rules.
- Projection output updates correctly across round reset/seal.
- No duplicate legacy side effects (score, notifications, kill fanout).

## Debugging
- Enable `t.db.roundevents 1` to inspect append order and event metadata.
