# Data Model: Combatant Concentration **Feature**: 018-combatant-concentration | **Date**: 2026-03-06 ## Entity Changes ### Combatant (modified) | Field | Type | Required | Default | Notes | |-------|------|----------|---------|-------| | id | CombatantId | yes | - | Existing, unchanged | | name | string | yes | - | Existing, unchanged | | initiative | number | no | undefined | Existing, unchanged | | maxHp | number | no | undefined | Existing, unchanged | | currentHp | number | no | undefined | Existing, unchanged | | ac | number | no | undefined | Existing, unchanged | | conditions | ConditionId[] | no | undefined | Existing, unchanged | | **isConcentrating** | **boolean** | **no** | **undefined (falsy)** | **New field. Independent of conditions.** | ### Domain Events (new) | Event | Fields | Emitted When | |-------|--------|-------------| | ConcentrationStarted | `type`, `combatantId` | Concentration toggled from off to on | | ConcentrationEnded | `type`, `combatantId` | Concentration toggled from on to off | ## State Transitions ``` toggleConcentration(encounter, combatantId) ├── combatant not found → DomainError("combatant-not-found") ├── isConcentrating is falsy → set to true, emit ConcentrationStarted └── isConcentrating is true → set to undefined, emit ConcentrationEnded ``` ## Validation Rules - `combatantId` must reference an existing combatant in the encounter. - No other validation needed (boolean toggle has no invalid input beyond missing combatant). ## Storage Impact - **Format**: JSON via localStorage (existing adapter). - **Migration**: None. Field is optional; absent field is treated as `false`. - **Backward compatibility**: Old data loads without `isConcentrating`; new data with the field serializes/deserializes transparently.