Skip to main content

Combat Rules and Engagements

This page owns Valenar's Force-scale combat primitive: the scope ContactEngagement that carries the persistent identity of a multi-Force engagement, its state-channel lifecycle, its template archetype, and the on_action transition events that fire across its state transitions. It is the systems-tier owner-doc for the contract that the Crown-scale strategic surface and the dynamic-quest-pressure planning layer consume.

Scope

This doc owns:

  • scope ContactEngagement as the persistent named engagement entity binding one-or-more participating Forces to a Province / Territory / Site anchor.
  • The EngagementState state channel and its six-rung lifecycle (Spotted, Engaged, Resolving, Resolved, Disengaged, Lost).
  • The ContactEngagement template archetype that supplies authored engagement-type metadata per the ad-0013 scope + state-channel + template-archetype canonical pattern.
  • Force lifecycle binding (Force.LifecycleState=Operating pairs with a ContactEngagement).
  • Operation.Status binding (ContactEngagement is a sub-state of the Operation's Main phase per gd-operations.md Decomposition Pattern phase 5).
  • The on_action transition labels (ContactEngagementSpotted, EngagementResolved, ForceDisengaged) that fire at state transitions.

This doc does NOT own:

  • Combat screen layout, animation pacing, watch/spectate flow (owned by ../ux/gd-combat-dungeon-screen.md; that doc consumes this systems-tier contract).
  • The MC-scale single-encounter resolver (already shipped under legacy/v1/examples/valenar/Host/Combat/CombatResolver.cs and Host/Combat/CombatRuntime.cs; see "Combat Resolution Surface" below for the explicit coexistence statement).
  • Encounter spec authoring (mob roster, stat blocks, terrain bonus catalog).
  • Force lifecycle (owned by gd-forces.md) and Operation lifecycle (owned by gd-operations.md); this doc cross-references both rather than re-authoring them.

Vocabulary

The following terms are the canonical combat-engagement surface across Valenar docs, Generated scope declarations, and the dynamic-quest- pressure source-fact contract.

  • ContactEngagement — a scope rooted at Province that carries the persistent identity of a multi-Force engagement. ContactEngagement is NOT a Modifier (Modifiers are reusable effect bundles per .claude/rules/secs-concepts.md; they have no identity, no anchor ids, no participant lists). ContactEngagement is NOT a per-tick event (per-tick events encode transitions, not the carrier of identity). ContactEngagement is the queryable engagement entity the Crown-scale surface, the dynamic-quest-pressure model, and the on_action event subscription layer all attach to.
  • EngagementState — the typed enum carried on the ContactEngagement state channel. Six discrete rungs (Spotted, Engaged, Resolving, Resolved, Disengaged, Lost). The state channel resolves through the 6-phase channel pipeline; transitions are applied by host-side engagement logic on the ContactEngagement scope row.
  • Spotted — first state. A ContactEngagement exists; one or more Forces have made contact with an opposing roster but have not yet committed to engagement. From Spotted the engagement may transition to Engaged (commit) or Disengaged (break contact).
  • Engaged — combat is active. Both sides are committed; resolution is underway. From Engaged the engagement may transition to Resolving (one side has signaled withdrawal or breaking) or directly to Resolved (decisive end on the current tick) or to Lost (the player's Force was annihilated).
  • Resolving — combat is winding down. One side is withdrawing, retreating, or pursuing. From Resolving the engagement may transition to Resolved (clean end), Disengaged (withdrawal succeeded without decisive end), or Lost.
  • Resolved — terminal state. The engagement ended with a decisive outcome; casualties, intelligence updates, and Modifier attachments are applied; the ContactEngagement row is retained for Journal readback and dynamic-quest-pressure history.
  • Disengaged — terminal state. The engagement ended without decisive outcome; one or more Forces broke contact and returned.
  • Lost — terminal state. The player's participating Force(s) were annihilated; ContactEngagement is retained for Journal readback; Force lifecycle transitions to Lost per gd-forces.md Force Lifecycle.

ContactEngagement Scope

ContactEngagement follows the canonical scope + state-channel + template-archetype pattern committed in ../lore/adr/ad-0013-scope-state-channel-template-archetype-pattern.md.

Scope declaration. scope ContactEngagement rooted at Province. ContactEngagement walks to Province (its anchor Province for the duration of the engagement). Field set committed in this doc:

  • AnchorTerritoryId — the Territory the engagement is bound to. A ContactEngagement spans a single Territory; cross-Territory pursuits resolve as separate ContactEngagement rows linked through ParticipantForceIds carryover.
  • AnchorSiteId — optional Site the engagement is anchored to. When present, the engagement is a Site-anchored engagement (gate- defense, dungeon raid, settlement raid). When absent, the engagement is open-field on the AnchorTerritory.
  • ParticipantForceIds: ScopedList<ForceRef> — the participating Force identities. ContactEngagement is multi-Force; a single ContactEngagement may bind several player Forces against a single threat roster, or several threat rosters against a single player Force.
  • EngagementState — the state channel (typed enum, six rungs per Vocabulary above). The state channel resolves through the 6-phase channel pipeline.
  • DefinitionTemplateId — the ContactEngagement template archetype this row was instantiated from (gate-defense, open-field, dungeon- raid, settlement-raid).

State channel. EngagementState is the canonical state channel for ContactEngagement, matching the Status/LifecycleState/Severity/Rank naming family committed in ad-0013. The channel is read via the 6-phase pipeline; transitions are applied by host-side engagement logic.

Template archetypes. template<ContactEngagement> archetypes supply authored engagement metadata: declared engagement kind (gate- defense, open-field, dungeon-raid, settlement-raid), the doctrine expectations for each side, the terrain bonus surface, and the binding rules for how a ContactEngagement row is created from a ThreatSource encounter (per ad-0008) or from an Operation Main-phase sub-activity (per gd-operations.md phase 5).

Engagement State Machine

The six EngagementState rungs transition through the following ordered pattern:

Spotted -> Engaged
Spotted -> Disengaged
Engaged -> Resolving
Engaged -> Resolved
Engaged -> Lost
Resolving -> Resolved
Resolving -> Disengaged
Resolving -> Lost

The exact transition predicates (the condition expressions that fire each transition) are deferred to the Wave 5a runtime-backing wave or its combat-runtime successor. This doc commits the state set, the allowed transition edges, and the on_action label set; the predicate authoring lives in the runtime-backing wave alongside the channel declarations themselves.

Once a ContactEngagement reaches Resolved, Disengaged, or Lost the row is terminal — no further transitions occur. The row is retained for Journal readback and dynamic-quest-pressure history; cleanup of terminal rows is governed by a Journal-retention policy authored alongside the runtime-backing wave.

Force Lifecycle Binding

Force.LifecycleState Operating (per gd-forces.md lines 255-270) pairs with one ContactEngagement at a time. The binding is:

  • When a Force enters Operating against a Threat (via an Operation), and the Operation reaches its Main phase (per gd-operations.md Decomposition Pattern phase 5 line 149), a ContactEngagement row is created with ParticipantForceIds containing that Force.
  • While the ContactEngagement is in Spotted, Engaged, or Resolving state, the participating Force remains in Operating lifecycle state.
  • When the ContactEngagement reaches Resolved, the participating Force transitions back to Available (engagement concluded decisively, Force retains organization).
  • When the ContactEngagement reaches Disengaged, the participating Force transitions to Returning (Force is withdrawing back to its origin or a safe staging point).
  • When the ContactEngagement reaches Lost, the participating Force transitions to Lost lifecycle state per gd-forces.md.

A Force participates in at most one ContactEngagement at a time. A ContactEngagement may bind multiple Forces; each bound Force shares the same Operating-to-terminal lifecycle transition pattern.

Operation.Status Binding

ContactEngagement is a sub-state of the Operation's Main phase per gd-operations.md Decomposition Pattern phase 5 line 149. The binding is:

  • An Operation enters its Main phase when the Operation's Force has reached the target Site / Territory and committed to the assigned activity (assault, besiege, cleanse, contain, garrison, march, intercept, escort, patrol).
  • A ContactEngagement row is created at Main-phase entry when the activity is contact-bearing (assault, besiege, cleanse, contain, intercept). Non-contact-bearing activities (garrison, march, escort, patrol) do not create a ContactEngagement unless contact is made during phase execution.
  • The ContactEngagement state machine progresses inside the Operation's Main phase. The Operation advances to the Consolidation phase when the ContactEngagement reaches Resolved or Disengaged.
  • An Operation whose ContactEngagement reaches Lost transitions directly to a Withdrawal/terminal sub-phase per gd-operations.md phase 7.

A single Operation may carry multiple ContactEngagement rows in sequence (pursuit, multi-stage assault) but only one ContactEngagement is active per Main-phase tick.

Combat Resolution Surface

ContactEngagement is the Force-scale combat primitive. It does NOT subsume or replace the MC-scale single-encounter resolver shipped at legacy/v1/examples/valenar/Host/Combat/CombatResolver.cs and Host/Combat/CombatRuntime.cs.

  • The MC-scale resolver continues to handle Act 0 dungeon-raid encounters and any other single-character vs. single-encounter combat. It reads MC channels (HP_Current, HP_Max, Attack, Armor) and runs an 8-round resolver, writing back HP_Current and XP. That surface remains correct for what it solves.
  • ContactEngagement is the missing layer above it: multi-Force engagements, where the participating units are Forces (with detachments, doctrine, officers, casualties at unit-scale rather than individual-hit-point scale).
  • The two coexist at different scales. A Site-anchored ContactEngagement (dungeon raid) may use the MC-scale resolver as its inner resolution mechanism when the player's participating Force consists of the MC alone or the MC plus a small retinue. When the participating Force is a multi-detachment unit, the ContactEngagement uses a Force-scale resolver authored in the combat-runtime wave.

This doc does NOT replace, deprecate, or subsume CombatResolver. CombatResolver stays; ContactEngagement layers above it.

on_action Transition Events

Three on_action transition labels fire at the corresponding EngagementState transitions. These are metadata labels for the existing SECS event-subscription machinery per .claude/rules/behavior-vocabulary.md and the on_action allowlist in .claude/rules/secs-concepts.md; they are not a new behavior keyword.

  • ContactEngagementSpotted(contactEngagementId) fires when a new ContactEngagement row is created in Spotted state. This is the earliest queryable transition the dynamic-quest-pressure model and the Crown-scale alert surface subscribe to.
  • EngagementResolved(contactEngagementId) fires when EngagementState transitions into Resolved. Casualties, intelligence updates, and Modifier attachments dispatch through this label.
  • ForceDisengaged(contactEngagementId, forceId) fires when a ContactEngagement transitions into Disengaged, or when an individual Force breaks contact from a multi-Force ContactEngagement before the engagement itself reaches a terminal state.

A EngagementLost(contactEngagementId) label is provisional; the combat-runtime wave decides whether Lost transitions emit through ForceDisengaged with a Lost-typed argument or through a dedicated label. The naming is deferred to that wave.

Runtime Backing Status

Status at this doc's acceptance time: contract-only.

  • Host/runtime owner: legacy/v1/examples/valenar/Host/Combat/ is the seed surface. The existing CombatResolver and CombatRuntime remain in place as the MC-scale resolver. A new ContactEngagementSystem host file is authored in a future combat-runtime wave alongside the new Host/Data/ContactEngagementData.cs.
  • Generated/.secs owner: the scope ContactEngagement declaration is authored in legacy/v1/examples/valenar/Content/combat/scopes.secs (new file in the combat-runtime wave); matching Generated stand-ins land in legacy/v1/examples/valenar/Generated/Combat/. The on_action declarations land alongside.
  • Read-model/UI owner: the Crown-scale strategic map surface and the combat-dungeon-screen UX (../ux/gd-combat-dungeon-screen.md) consume ContactEngagement rows for engagement readback.
  • Tests: deferred to the future combat-runtime wave.
  • Known gaps: the transition predicate authoring (which condition fires Spotted -> Engaged, which fires Engaged -> Resolving, etc.); the Force-scale resolver inside Engaged state; the ContactEngagement template archetype catalog; the casualty computation surface for Resolved transitions; the Modifier attachment surface for engagement-induced effects (fatigue, supply drain, doctrine wear).
  • Current backing today: no ContactEngagement scope exists; no EngagementState channel exists; no Host/Data/ContactEngagementData.cs exists. The MC-scale CombatResolver and CombatRuntime exist but resolve a different scale of combat.
  • Illegal fallback behavior: no per-tick aggregator that pretends multi-Force combat resolves through a single CombatResolver invocation. No silent fold of multi-Force engagements into the MC-scale resolver. No Modifier-as-engagement-carrier (e.g., a Modifier(ActiveEngagement) attached to a Force) standing in for the ContactEngagement scope. No fifth scope-state shape outside the ad-0013 canonical pattern.
  • Next closure wave: a future combat-runtime wave authors the scope, the state channel, the template archetypes, the on_action declarations, the host data classes, and the Force-scale resolver inside Engaged state.

Glossary Propagation

The following terms MUST land in gd-glossary.md, gd-canon.md, and README.md:

  • ContactEngagement (scope primitive).
  • EngagementState (state channel).
  • Spotted, Engaged, Resolving, Resolved, Disengaged, Lost (the six EngagementState rungs).
  • ContactEngagementSpotted, EngagementResolved, ForceDisengaged (on_action transition labels).

AAA Precedent

The ContactEngagement primitive draws on two AAA references:

  • CK3 Battle (primary) — Crusader Kings 3's Battle is the persistent named multi-phase engagement entity with the strongest pedigree for the ContactEngagement shape. CK3 Battle carries phase (Tactic Selection / Skirmish / Main Phase / Pursuit / Resolved), participants, tactic selection, terrain bonus, modifier stack, and resolves into Casualties on either side. The six-rung Valenar EngagementState mirrors the phase structure: Spotted matches CK3's pre-battle Tactic Selection, Engaged matches Main Phase, Resolving matches Pursuit, Resolved/Disengaged/Lost match CK3's terminal branches.
  • Total War campaign-map Contact (secondary) — Total War campaign-scale ContactEvent between agents and armies is the precedent for the Spotted -> Engaged transition: an agent or army enters detection range, the campaign-map surfaces the contact, and the player either commits (Engaged) or breaks contact (Disengaged). Total War's named-entity persistence across the campaign turn matches the ContactEngagement carry-over from Spotted through to terminal states.

RimWorld's Lord system (an AI controller persisting across encounter ticks) is the precedent for ContactEngagement-as-AI-anchor: the ContactEngagement scope is the persistent anchor a future Force-scale AI policy reads to decide assault, retreat, or hold-the-line.

Cross-References