# Host Migration Session Schema

## Purpose
This document defines the v1 host-migration contract and the durable session schema for TerrorTown.

The design goal is not live round preservation. The design goal is:

1. the host actually leaves, disconnects, or crashes,
2. the current round is aborted,
3. the new host completes a controlled cleanup and recovery pass,
4. the game returns to the normal waiting or preround flow,
5. cross-round match data survives without corruption.

This keeps migration scoped to the match shell while still shaping the session model so future moderation and administration work can be additive instead of destructive.

## Policy
- Host migration is host-left-only recovery.
- `AutoSwitchToBestHost` must remain disabled.
- We do not support proactive best-host rotation.
- We do not attempt to preserve active round world state.
- We do not attempt to preserve active round inventory, corpses, dropped items, planted equipment, body inspection state, or mutable map state.
- We do preserve cross-round session data, authority data, sanctions, reports, round records, and vote cooldowns.

## Recovery contract
When `OnBecameHost` fires and the previous host is gone:

1. Enter recovery mode and lock risky gameplay mutations.
2. If the previous state was `Starting`, `Started`, or `Ending`, mark that round as `AbortedByHostMigration`.
3. Cancel active RTV and map-vote runtime instead of trying to resume them.
4. Transform unresolved reports from the aborted round into a safe closed state.
5. Preserve already-issued sanctions and cross-round player data.
6. Run the normal round cleanup barrier.
7. Return to `WaitingForPlayers` or resume the standard preround loop.
8. Exit recovery mode and continue the ordinary game loop.

`Ended` is excluded from the round-abort transform because the round result has already been sealed and the game may be in map-vote or map-transfer flow. If a prepared map switch exists, host recovery should continue that map switch; otherwise it should recover the match shell without rewriting the completed round as a host-migration abort.

## What persists
- Match identity and recovery bookkeeping.
- Current map ident, current round number, and stable round id.
- Runtime rules needed to continue the match.
- Connected and recently-disconnected player session records.
- Session stats.
- AFK state.
- Authority assignments, groups, overrides, and host-authority policy.
- Active sanctions and moderation audit records.
- Reports and their final resolution records.
- Round records that reports and review flows can link against.
- Vote cooldowns that should survive recovery.

## What resets
- The active round itself.
- Team assignments and roles for the aborted round.
- Live pawns, corpses, body inspection state, and corpse-only metadata.
- Active inventory state, ammo, credits, dropped weapons, and planted equipment.
- Active map vote options and collected votes.
- Active RTV vote options and collected votes.
- Mutable world state such as doors, props, glass, buttons, role buttons, moving entities, and round-spawned scene objects.
- Mid-round karma pending deltas that have not yet been applied to a player's session karma.

## Authority rules
- `MatchSessionState` becomes the canonical cross-round session root.
- Runtime systems become projections and writers into that root.
- Technical host authority is not the same thing as moderator or owner authority.
- Human players are keyed by `SteamId`.
- Bots are keyed by a session-local `SessionBotId`.
- Runtime-only object ids such as `PlayerId`, `PawnComponentId`, `GameObject.Id`, and scene object references are not session authority.

## Root component
The root component is defined in `Code/Game/Host/Session/MatchSessionState.Schema.cs`.

Top-level synced header fields:

- `SchemaVersion`
- `MatchId`
- `SnapshotRevision`
- `RecoveryEpoch`
- `RecoveryInProgress`
- `LastKnownGameState`
- `CurrentRoundNumber`
- `CurrentRoundId`
- `CurrentMapIdent`
- `RulesJson`
- `PlayersJson`
- `PermissionsJson`
- `SanctionsJson`
- `ReportsJson`
- `RoundsJson`
- `VotingJson`

Header field intent:

- `SchemaVersion`: explicit migration format version.
- `MatchId`: stable identifier for the current lobby match.
- `SnapshotRevision`: increment whenever authoritative session state changes.
- `RecoveryEpoch`: increment after each successful host-left recovery.
- `RecoveryInProgress`: true only while the new host is aborting and rebuilding the match shell.
- `LastKnownGameState`: the last host-authored `GameMode.State` before or during takeover.
- `CurrentRoundNumber`: the match round counter.
- `CurrentRoundId`: stable id for the round currently in progress or being aborted.
- `CurrentMapIdent`: the current map or scene ident for sanity checks and round records.

## JSON documents
All JSON uses the C# property names from `MatchSessionState.Schema.cs`.

### `RulesJson`
Serialized type: `MatchSessionRulesState`

Owns the runtime rules required to continue the same match after recovery.

Fields:
- `MaxPlayers`
- `MinPlayerCount`
- `MaxRounds`
- `WaitPollSeconds`
- `WaitRecoverySeconds`
- `PrepareDurationSeconds`
- `NextRoundDelaySeconds`
- `HasteMode`
- `HasteStartingMinutes`
- `HasteMinutesPerDeath`
- `RoundTimeMinutes`
- `MapVoteMode`
- `IncludeBlacklistInWhitelist`
- `MapVoteOptionCount`
- `MapVoteWhitelist`
- `MapVoteBlacklist`
- `HostMigrationPolicy`

Rules notes:
- `AutoSwitchToBestHost` is not session state. Lobby creation must keep it disabled so v1 never enters proactive best-host rotation.
- `HostMigrationPolicy` is explicit so the recovery behavior is part of the match contract, not an unwritten assumption.

### `PlayersJson`
Serialized type: `MatchSessionPlayersState`

Owns the stable player registry for the match and the next session bot id.

Fields:
- `NextSessionBotId`
- `Players`

Each player entry is `MatchSessionPlayerState`:
- `Actor`
- `DisplayName`
- `Presence`
- `IsAFK`
- `LastConnectedAtUnixSeconds`
- `LastDisconnectedAtUnixSeconds`
- `Stats`
- `Karma`

`Actor` is `MatchSessionActorRef`:
- `Kind`
- `SteamId`
- `SessionBotId`

`Stats` is `MatchSessionStatsState`:
- `Kills`
- `Deaths`
- `Assists`
- `TeamKills`
- `Suicides`
- `RoundsPlayed`
- `Score`

`Karma` is `MatchSessionKarmaState`:
- `Actual`
- `Visible`

Player notes:
- This document is for player-owned session state, not punishment state.
- Round-start slays, skip-round penalties, bans, gags, mutes, and gimps are modeled in `SanctionsJson`, not in the player record.

### `PermissionsJson`
Serialized type: `MatchSessionPermissionsState`

Owns durable in-session authority, roles, and privilege rules.

Fields:
- `FounderActor`
- `OwnerActor`
- `HostAuthorityPolicy`
- `DefaultGroupId`
- `Groups`
- `Assignments`
- `ActorOverrides`

Each group entry is `MatchSessionPermissionGroup`:
- `GroupId`
- `DisplayName`
- `IsBuiltin`
- `DisplayColorHex`
- `DisplayIconName`
- `ImmunityLevel`
- `InheritedGroupIds`
- `PermissionRules`
- `TargetPolicy`

Each permission rule is `MatchSessionPermissionRule`:
- `Effect`
- `PermissionKey`
- `ConstraintTag`

Each assignment is `MatchSessionPermissionAssignment`:
- `Actor`
- `GroupIds`
- `AssignedBy`
- `AssignedAtUnixSeconds`
- `ExpiresAtUnixSeconds`
- `Note`

Each actor override is `MatchSessionActorPermissionOverride`:
- `Actor`
- `Rules`
- `AssignedBy`
- `AssignedAtUnixSeconds`
- `ExpiresAtUnixSeconds`
- `Note`

`TargetPolicy` is `MatchSessionTargetPolicy`:
- `AllowSelfTargeting`
- `MaxTargetImmunityLevel`
- `AllowedGroupIds`
- `DeniedGroupIds`

Permissions notes:
- This replaces the shallow `AdminSteamIds` model.
- Temporary moderators are naturally modeled as expiring assignments.
- Host transport authority and session moderator authority are intentionally separate concerns.
- The initial built-in ids and capability keys are centralized in `Code/Game/Host/Session/MatchSessionPermissionsCatalog.cs`.

### `SanctionsJson`
Serialized type: `MatchSessionSanctionsState`

Owns active sanctions and moderation audit history.

Fields:
- `ActiveSanctions`
- `ActionAudit`

Each sanction entry is `MatchSessionSanctionRecord`:
- `SanctionId`
- `Type`
- `Target`
- `Scope`
- `Status`
- `Quantity`
- `IssuedBy`
- `IssuedAtUnixSeconds`
- `ExpiresAtUnixSeconds`
- `Reason`
- `ModeratorNote`
- `Origin`
- `RevokedBy`
- `RevokedAtUnixSeconds`
- `RevocationNote`

`Origin` is `MatchSessionSanctionOrigin`:
- `Kind`
- `RelatedReportId`
- `RelatedRoundId`
- `RelatedActionId`
- `Note`

Each audit entry is `MatchSessionModerationActionRecord`:
- `ActionId`
- `Type`
- `PerformedBy`
- `Target`
- `OccurredAtUnixSeconds`
- `Summary`
- `Reason`
- `ModeratorNote`
- `RelatedSanctionId`
- `RelatedReportId`

Sanctions notes:
- `Kick` is modeled as an audit action, not an active sanction.
- Slays, skip-round penalties, bans, gags, mutes, and gimps all live here, even if some are not wired yet.
- `Scope` reserves the boundary between match-local, lobby-local, and future profile-backed sanctions without mixing those systems together today.

### `ReportsJson`
Serialized type: `MatchSessionReportsState`

Owns report workflow, response data, resolution records, and evidence references.

Fields:
- `Reports`

Each report entry is `MatchSessionReportRecord`:
- `ReportId`
- `Reporter`
- `ReporterName`
- `Reported`
- `ReportedName`
- `RoundNumber`
- `RoundId`
- `ReporterReason`
- `Workflow`
- `Response`
- `Resolution`
- `EvidenceRefs`

`Workflow` is `MatchSessionReportWorkflow`:
- `State`
- `CreatedAtUnixSeconds`
- `AssignedModerator`
- `AssignedAtUnixSeconds`
- `ClosedAtUnixSeconds`

`Response` is `MatchSessionReportResponse`:
- `ResponseRequested`
- `RequestedAtUnixSeconds`
- `ReportedResponseText`
- `RespondedAtUnixSeconds`

`Resolution` is `MatchSessionReportResolution`:
- `Disposition`
- `ResolvedBy`
- `ResolvedAtUnixSeconds`
- `ModeratorNote`
- `AppliedSanctionIds`

Each evidence ref is `MatchSessionEvidenceRef`:
- `Kind`
- `RoundId`
- `ExternalId`
- `Summary`

Report notes:
- Workflow state is separate from outcome.
- Resolution is separate from punishment.
- Applied punishments are linked by sanction ids instead of being embedded into a status enum.
- On host migration, unresolved reports from the aborted round can close as `VoidedByHostMigration` without losing already-issued sanctions.

### `RoundsJson`
Serialized type: `MatchSessionRoundsState`

Owns compact round records that report review and future archives can target.

Fields:
- `Rounds`

Each round entry is `MatchSessionRoundRecord`:
- `RoundId`
- `RoundNumber`
- `Resolution`
- `WinningTeam`
- `DurationSeconds`
- `MapIdent`
- `StartedAtUnixSeconds`
- `EndedAtUnixSeconds`
- `Summary`
- `ArchiveKey`

Round notes:
- This replaces the thin round history list with a proper stable round identity.
- `ArchiveKey` is intentionally lightweight and reserved for future off-root archival data such as evidence bundles or damagelog references.

### `VotingJson`
Serialized type: `MatchSessionVotingState`

Owns only the small pieces of voting state that should survive recovery.

Fields:
- `VoteKickCooldownEndsAtUnixSeconds`
- `RockTheVoteCooldownEndsAtUnixSeconds`

Voting notes:
- Active vote options, active vote tallies, and pending transfer state are intentionally not persisted.
- Recovery cancels active RTV and map vote runtime, then normal flow can create new vote objects later.

## Runtime mapping
This is the intended ownership map for the refactor.

- `Code/Game/Round/GameMode.cs`
  - owns recovery entry and exit, updates `LastKnownGameState`, `CurrentRoundNumber`, `CurrentRoundId`, `CurrentMapIdent`, `RecoveryEpoch`, `RecoveryInProgress`, and appends aborted round records.
- `Code/Game/Round/RoundRulesSystem.cs`
  - writes timing and rule fields into `RulesJson`.
- `Code/Game/Host/Session/LobbyStartSettings.cs`
  - seeds initial rules. Host-switch transport policy stays outside persisted lobby settings.
- `Code/Players/ConnectedClient.cs`
  - remains the runtime player object, but cross-round truth moves to `PlayersJson`.
- `Code/Players/ConnectedClient.GameStats.cs`
  - current static session stats dictionary should be replaced by `PlayersJson`.
- `Code/Game/Host/Session/AdminSystem.cs`
  - local bootstrap is still useful, but active in-lobby authority moves into `PermissionsJson`.
- `Code/Game/Host/Network/GameNetworkManager.Moderation.cs`
  - current host-local moderation state splits between `PermissionsJson` and `SanctionsJson`.
- `Code/Game/Moderation/Reports/ReportSystem.cs`
  - current report list and slay queue move into `ReportsJson` plus sanction records in `SanctionsJson`.
- `Code/Game/Voting/MapVoteSystem.cs`
  - keeps rule configuration in `RulesJson`; active vote runtime remains disposable.
- `Code/Game/Voting/RTVSystem.cs`
  - active vote runtime remains disposable, but cooldowns move into `VotingJson`.

## Recovery transforms
When host-left recovery begins:

- `RecoveryInProgress` becomes `true`.
- `LastKnownGameState` is sampled before state transitions are rewritten.
- If the last known state was `Starting`, `Started`, or `Ending`, append `AbortedByHostMigration` to `RoundsJson`.
- Mark unresolved reports for the aborted round as closed with `VoidedByHostMigration`.
- Carry forward authority assignments, sanctions, player stats, player karma, and vote cooldowns.
- Discard active round runtime and let the normal cleanup barrier rebuild the next safe state.

When recovery completes:

- `RecoveryInProgress` becomes `false`.
- `RecoveryEpoch` increments.
- `SnapshotRevision` increments again after the rebuilt match shell is authoritative.

## Explicit non-goals for v1
- Mid-round role preservation.
- Mid-round corpse preservation.
- Mid-round body inspection preservation.
- Mid-round inventory preservation.
- Mid-round prop or map-object preservation.
- Mid-round dropped item preservation.
- Mid-round C4 or explosive preservation.
- Resuming a live RTV or map vote.
- Full persistent profile authority.
- Heavy evidence archives inside the replicated session root.
- Any periodic best-host rotation behavior.

## Review notes
- This schema intentionally prefers correct authority boundaries over convenience fields.
- The important split is now player state vs permissions vs sanctions vs reports.
- The round-abort policy is what lets us keep world state and live round state out of the critical path.
- If dedicated servers become the main mode later, this session shape still pays off because the cross-round shell is centralized instead of scattered across host-local statics.
