Skip to main content

State Machines and Transitions

Context

This page is the binding hub doc for every named state machine in Valenar. Foundation Hardening (Wave 3a) introduced four new state-machine families through the cross-cutting canonical-pattern ADR ../lore/adr/ad-0013-scope-state-channel-template-archetype-pattern.md: the Quest Thread / Mission lifecycle pair (ad-0007), the ThreatSource / FrontCandidate emergence ladder (ad-0008), the Realm PolityRank ladder (ad-0009), and the Act Progression ladder (ad-0010). The True Harm placeholder convention (ad-0011) and the character-conditions-and-injuries Modifier-template shape (ad-0012) land alongside that quartet.

The new families do not stand alone. The existing Front lifecycle (FrontStatus enum on Content/forces/scopes.secs), the Force lifecycle (LifecycleState enum on the same scope file), the Operation Phase and Status enums (also on Content/forces/scopes.secs), and the three discovery / knowledge / content state axes from ./gd-state-axes-and-thresholds.md are existing precedents that this hub must also bind into one mechanical table.

The Foundation Hardening goal is concrete: every transition row in Valenar carries an explicit predicate, an explicit trigger, explicit required facts, named Config thresholds, named side-effects, named runtime owner, and an explicit fallback. No silent default transitions. No day-count predicates. No real-time clock predicates. No magic threshold constants. Where the originating ADR explicitly invited inline authoring this wave, the row carries the committed predicate. Where the originating ADR explicitly deferred the predicate to Wave 5a runtime-backing closure, the row carries the canonical <deferred-to-Wave-5a-runtime-backing> marker plus a one-line design-intent note sourced from the ADR.

Scope

This doc OWNS:

  • The canonical 12-column transition row format used by every state machine table below.
  • The canonical <deferred-to-Wave-5a-runtime-backing> marker form for Wave 5a closure handoffs.
  • The aggregated Open Follow-On Items list at the bottom of the doc: the canonical deferral registry that Wave 5a authors consume to plan the runtime-backing work.

This doc CITES (does not re-author):

  • Per-state-machine state enumerations: each owner doc (gd-quest-thread- and-mission-state-machine.md, gd-front-emergence.md, gd-act-progression.md, gd-character-conditions-and-injuries.md, gd-realm-ranks-and-polity.md, gd-defense-zones.md, gd-fronts.md, gd-forces.md, gd-operations.md, gd-state-axes-and-thresholds.md) holds the design rationale and the committed enum labels.
  • Per-Act gate predicates: the seven ../acts/gd-act-N-gate-spec.md docs each own their own predicate. The Act ladder section below cites the gate-spec docs by name in the trigger_event_or_predicate cell; it does not duplicate the predicate text.
  • Per-MC-condition ladder rungs: gd-character-conditions-and-injuries.md owns the rung labels. The MC Condition Ladders section below cites that doc; it does not re-author the rungs.

Column Schema

Every transition table uses the same 12-column row form. One logical transition per row. The column rules are normative.

ColumnContent rule
source_stateThe named enum value or scope state the row leaves. MUST be a committed value (see ad-0007 / ad-0008 / ad-0009 / ad-0010 / Content/forces/scopes.secs / Generated/Territories/FeaturePlacement/FeaturePlacementTypes.cs / Generated/Territories/TerritoryKnowledgeTypes.cs / Generated/Territories/FeaturePlacement/FeatureContentTypes.cs / gd-fronts.md / gd-act-0-mission-spine.md). Inventing a state is a forbidden-pattern FAIL.
trigger_event_or_predicateThe on_action label OR named state predicate (e.g., core_established == true, Realm.CoveredSettlements.Count >= 2 && RouteCoverage(Realm)) that drives the transition. NEVER a day count, NEVER a real-time clock value (per ad-0010 lines 96-99). If the originating ADR defers the predicate to Wave 5a, the cell carries the canonical <deferred-to-Wave-5a-runtime-backing> marker plus a one-line design-intent note.
required_factsThe named state predicates that must read true at the trigger moment. Each fact names an existing channel, scope field, or enum read — no invented facts. Example: MainCharacter exists && CoreBuilding.IsBuilt && Settlement.Stage >= I.
config_thresholdsNamed Config.<Subsystem>.<FieldName> references (UI-exposed Config fields with world-size-parameterized defaults). Magic constants are a forbidden-pattern FAIL. If the threshold's Config field name is not yet declared, cell is <deferred-to-Wave-5a-runtime-backing>.
blocking_conditionsNamed predicates that, if true, refuse the transition even when the trigger fires. Example: MissionLifecycleState == Suspended blocks Active → Resolved.
side_effectsThe complete list of events fired (on_action labels with (working name — promotion to committed metadata deferred to Wave 5a) annotation where applicable), Journal entries written (specific entry_id shape per gd-journal.md), Modifier applies / clears, scope rows created / retired, channel writes. Each line is one mechanical effect, not prose.
target_stateThe committed enum value or scope state the row enters.
runtime_ownerThe proposed Host/Systems/<Name>.cs class that owns the body. None of these systems exist today; each is a Wave 5a follow-up per the originating ADR's "Next closure wave" line.
read_model_surfaceThe Host/ReadModels/<Name>.cs projection that surfaces the new state. Some surfaces already exist as files but project nothing today (per gd-forces-phase-b-design-questions.md line 492 — ForcesReadModel, FrontsReadModel, OperationsReadModel, DefenseZonesReadModel, ProvinceDefensePlansReadModel). For new state machines, name the proposed read model and mark <contract-only; runtime-backing in Wave 5a>.
ui_readbackThe client screen(s) under ux/ that surface this transition.
tests_requiredPlaceholder format <deferred-to-wave-7>: <PascalCaseTransitionTestName>.
fallback_behaviorExplicit error per the wording bank below. Silent defaults are forbidden.

Sample Row

source_statetriggerrequired_factsconfig_thresholdsblocking_conditionsside_effectstarget_stateruntime_ownerread_modelui_readbacktestsfallback
QuestThreadLifecycleState.HiddenDiscovery predicate from template<QuestThread>.DiscoveryTrigger evaluates true under dynamic quest pressure generator output (per ad-0007 Consequences paragraph 2 and gd-dynamic-quest-pressure-model.md generator contract)template<QuestThread> registered; scope QuestThread row exists with DefinitionTemplateId valid; ActState.CurrentAct >= template.MinAct; template's DiscoveryTrigger predicate (named state, never a day count) reads trueConfig.QuestThread.DiscoveryEvaluationCadencenoneFires on_action QuestThreadAvailable(threadId) (working name; promotion to committed metadata deferred to Wave 5a per ad-0007); Journal entry of class quest-available per gd-journal.md; Current Plan urgency rank updated per gd-current-plan.mdQuestThreadLifecycleState.AvailableHost/Systems/QuestThreadSystem.cs (proposed; contract-only per ad-0007)Host/ReadModels/QuestThreadsReadModel.cs (proposed; contract-only)gd-current-plan.md, gd-objectives-screen.md, gd-journal.md<deferred-to-wave-7>: QuestThreadHiddenToAvailableTestthrow InvalidStateException("Required predicate 'template.DiscoveryTrigger' not present at transition Hidden -> Available for QuestThreadId=<id>")

Fallback Wording Bank

Every fallback_behavior cell commits to one of these explicit-error forms (per .claude/rules/valenar-contract-backing.md gate item 4 — silent fallbacks are forbidden). Exception type names are proposals; the Wave 5a implementer consults legacy/v1/src/SECS.Engine/ for the committed convention before locking the names.

SituationWording
Missing required fact at transition timethrow InvalidStateException("Required fact 'X' not present at transition Y -> Z for <ScopeKind>Id=<id>")
Missing Config field at runtimethrow ConfigurationException("Config field 'X' not registered before <Subsystem>Tick")
Blocking condition met (transition refused)Transition refused; remains in source_state; raises on_action BlockedTransition(scopeId, sourceState, attemptedTarget, blockingCondition) and writes Journal entry of class transition-blocked.
Ambiguous transitions (two predicates both fire)throw AmbiguousTransitionException("Multiple eligible transitions from <sourceState>: <list>")
Target state unreachable from currentthrow IllegalTransitionException("X is not a valid target from Y in <state-machine-name>")
Required runtime owner not registered at startupthrow StartupValidationException("<HostSystem>.cs registration missing; required by <state-machine-name>") (checked at SecsModule.Initialize time)
Required scope row missingthrow MissingScopeException("scope <Kind> row required but not present for transition X -> Y")
Defense-Province with no committed planthrow ConfigurationException("Province <id> has no committed ProvinceDefensePlan in Committed state; <Subsystem> requires a plan in Committed before <activity>")

State Machines

Quest Thread Lifecycle

Owner doc: gd-quest-thread-and-mission-state-machine.md. Binding ADR: ../lore/adr/ad-0007-quest-thread-and-mission-scope-primitives.md.

States (per ad-0007 lines 67-70): Hidden, Available, Active, Suspended, Resolved, Failed, Abandoned.

source_statetriggerrequired_factsconfig_thresholdsblocking_conditionsside_effectstarget_stateruntime_ownerread_modelui_readbacktestsfallback
Hiddentemplate<QuestThread>.DiscoveryTrigger predicate reads true under dynamic quest pressure generator output (per ad-0007 Consequences paragraph 2 and gd-dynamic-quest-pressure-model.md)template registered; scope row with valid DefinitionTemplateId; ActState.CurrentAct >= template.MinAct; DiscoveryTrigger predicate reads trueConfig.QuestThread.DiscoveryEvaluationCadencenoneFires on_action QuestThreadAvailable(threadId) (working name — promotion deferred to Wave 5a); Journal entry quest-available; Current Plan urgency updatedAvailableHost/Systems/QuestThreadSystem.cs (proposed)Host/ReadModels/QuestThreadsReadModel.cs (proposed; contract-only)gd-current-plan.md, gd-objectives-screen.md, gd-journal.md<deferred-to-wave-7>: QuestThreadHiddenToAvailableTestthrow InvalidStateException("Required predicate 'template.DiscoveryTrigger' not present at transition Hidden -> Available for QuestThreadId=<id>")
AvailablePlayer or policy accepts a child Mission under this thread (per ad-0007 Consequences paragraph 2)child scope Mission row exists in Accepted state with ParentThreadId == this.Idnone today (acceptance shape <deferred-to-Wave-5a-runtime-backing> per ad-0007 line 56-57)thread already Suspended from a prior precondition violationFires on_action QuestThreadActivated(threadId) (working name); Journal entry quest-activated; Current Plan promotes thread to active rankActiveHost/Systems/QuestThreadSystem.cs (proposed)Host/ReadModels/QuestThreadsReadModel.cs (proposed)gd-current-plan.md, gd-journal.md<deferred-to-wave-7>: QuestThreadAvailableToActiveTestthrow InvalidStateException("No Accepted Mission with ParentThreadId=<id> at transition Available -> Active")
ActivePrecondition predicate authored on template<QuestThread>.SuspensionPredicate reads true (<deferred-to-Wave-5a-runtime-backing> — precondition violation per ad-0007 line 56-57)template's SuspensionPredicate predicate reads true<deferred-to-Wave-5a-runtime-backing>noneFires on_action QuestThreadSuspended(threadId, reasonId) (working name); Journal entry quest-suspended; Current Plan demotes urgency rankSuspendedHost/Systems/QuestThreadSystem.cs (proposed)Host/ReadModels/QuestThreadsReadModel.cs (proposed)gd-current-plan.md, gd-journal.md<deferred-to-wave-7>: QuestThreadActiveToSuspendedTestthrow InvalidStateException("Suspension predicate not defined on template for QuestThreadId=<id>")
SuspendedSame precondition predicate reads false again (<deferred-to-Wave-5a-runtime-backing> — precondition restored per ad-0007 line 56-57)template's SuspensionPredicate reads false<deferred-to-Wave-5a-runtime-backing>noneFires on_action QuestThreadResumed(threadId) (working name); Journal entry quest-resumed; Current Plan restores urgency rankActiveHost/Systems/QuestThreadSystem.cs (proposed)Host/ReadModels/QuestThreadsReadModel.cs (proposed)gd-current-plan.md, gd-journal.md<deferred-to-wave-7>: QuestThreadSuspendedToActiveTestthrow InvalidStateException("Resume predicate not defined on template for QuestThreadId=<id>")
ActiveAll required child Missions resolved per template<QuestThread>.ResolutionRule (<deferred-to-Wave-5a-runtime-backing> — resolution predicate per ad-0007 line 56-57)every required child Mission has LifecycleState == Resolved<deferred-to-Wave-5a-runtime-backing>any required child Mission in Failed and template marks it critical-pathFires on_action QuestThreadResolved(threadId) (working name); Journal entry quest-resolved; Current Plan retires entryResolvedHost/Systems/QuestThreadSystem.cs (proposed)Host/ReadModels/QuestThreadsReadModel.cs (proposed)gd-current-plan.md, gd-journal.md<deferred-to-wave-7>: QuestThreadActiveToResolvedTestthrow InvalidStateException("ResolutionRule not defined on template for QuestThreadId=<id>")
ActiveAny required child Mission with LifecycleState == Failed and template<QuestThread>.ResolutionRule marks it critical-path (<deferred-to-Wave-5a-runtime-backing> — failure predicate per ad-0007 line 56-57)child Mission LifecycleState == Failed; thread's template marks that mission slot critical-path<deferred-to-Wave-5a-runtime-backing>noneFires on_action QuestThreadFailed(threadId, failedMissionId) (working name); Journal entry quest-failed; Current Plan retires entryFailedHost/Systems/QuestThreadSystem.cs (proposed)Host/ReadModels/QuestThreadsReadModel.cs (proposed)gd-current-plan.md, gd-journal.md<deferred-to-wave-7>: QuestThreadActiveToFailedTestthrow InvalidStateException("Critical-path slot annotation missing on ResolutionRule for QuestThreadId=<id>")
ActivePlayer explicitly abandons the thread (activity binding <deferred-to-Wave-5a-runtime-backing> — abandonment activity per ad-0007 line 56-57)abandonment activity issued by player or Crown-scale policynonenoneFires on_action QuestThreadAbandoned(threadId) (working name); Journal entry quest-abandoned; Current Plan retires entryAbandonedHost/Systems/QuestThreadSystem.cs (proposed)Host/ReadModels/QuestThreadsReadModel.cs (proposed)gd-current-plan.md, gd-journal.md<deferred-to-wave-7>: QuestThreadActiveToAbandonedTestthrow InvalidStateException("Abandonment activity not bound for QuestThreadId=<id>")
AvailablePlayer dismisses the thread before accepting any Mission under it (activity binding <deferred-to-Wave-5a-runtime-backing> — dismissal activity per ad-0007 line 56-57)dismissal activity issued by playernonenoneFires on_action QuestThreadAbandoned(threadId) (working name); Journal entry quest-dismissedAbandonedHost/Systems/QuestThreadSystem.cs (proposed)Host/ReadModels/QuestThreadsReadModel.cs (proposed)gd-current-plan.md, gd-journal.md<deferred-to-wave-7>: QuestThreadAvailableToAbandonedTestthrow InvalidStateException("Dismissal activity not bound for QuestThreadId=<id>")

Mission Lifecycle

Owner doc: gd-quest-thread-and-mission-state-machine.md. Binding ADR: ../lore/adr/ad-0007-quest-thread-and-mission-scope-primitives.md.

States (per ad-0007 lines 70-71): Pending, Available, Accepted, Active, Resolved, Failed, Abandoned.

source_statetriggerrequired_factsconfig_thresholdsblocking_conditionsside_effectstarget_stateruntime_ownerread_modelui_readbacktestsfallback
PendingParent scope QuestThread enters Active AND mission's template<Mission>.PreconditionPredicate reads true (<deferred-to-Wave-5a-runtime-backing> — precondition predicate per ad-0007 line 56-57)parent QuestThread LifecycleState == Active; mission's preconditions read true<deferred-to-Wave-5a-runtime-backing>parent thread in SuspendedFires on_action MissionAvailable(missionId, parentThreadId) (working name); Journal entry mission-available; Objectives screen surfaces missionAvailableHost/Systems/MissionSystem.cs (proposed)Host/ReadModels/MissionsReadModel.cs (proposed; contract-only)gd-objectives-screen.md, gd-journal.md<deferred-to-wave-7>: MissionPendingToAvailableTestthrow InvalidStateException("Parent QuestThread not Active at transition Pending -> Available for MissionId=<id>")
AvailablePlayer or policy commits the mission (activity binding <deferred-to-Wave-5a-runtime-backing> per ad-0007 line 56-57)acceptance activity issuednoneparent thread in SuspendedFires on_action MissionAccepted(missionId) (working name); Journal entry mission-accepted; Current Plan queues missionAcceptedHost/Systems/MissionSystem.cs (proposed)Host/ReadModels/MissionsReadModel.cs (proposed)gd-objectives-screen.md, gd-current-plan.md, gd-journal.md<deferred-to-wave-7>: MissionAvailableToAcceptedTestthrow InvalidStateException("Acceptance activity not bound for MissionId=<id>")
AcceptedActivity executor begins running the mission's authored activity beats (per gd-queue-and-activity-execution.md)mission row's first ActivityBeatRef enters executor; mission queued by the executornoneexecutor at capacity (<deferred-to-Wave-5a-runtime-backing>)Fires on_action MissionActivated(missionId) (working name); Journal entry mission-active; Current Plan promotes mission rankActiveHost/Systems/MissionSystem.cs (proposed)Host/ReadModels/MissionsReadModel.cs (proposed)gd-current-plan.md, gd-journal.md<deferred-to-wave-7>: MissionAcceptedToActiveTestthrow InvalidStateException("ActivityBeatRef list empty at transition Accepted -> Active for MissionId=<id>")
ActiveAll required activity beats resolved per template<Mission>.ResolutionRule (<deferred-to-Wave-5a-runtime-backing> — resolution predicate per ad-0007 line 56-57)every required beat resolved<deferred-to-Wave-5a-runtime-backing>any required beat in Failed and template marks it critical-pathFires on_action MissionResolved(missionId) (working name); Journal entry mission-resolved; Current Plan retires mission; parent QuestThread re-evaluates ResolutionRuleResolvedHost/Systems/MissionSystem.cs (proposed)Host/ReadModels/MissionsReadModel.cs (proposed)gd-current-plan.md, gd-journal.md<deferred-to-wave-7>: MissionActiveToResolvedTestthrow InvalidStateException("ResolutionRule not defined on template for MissionId=<id>")
ActiveAny required activity beat with Failed outcome and template marks it critical-path (<deferred-to-Wave-5a-runtime-backing> — failure predicate per ad-0007 line 56-57)required beat in Failed; template marks beat critical-path<deferred-to-Wave-5a-runtime-backing>noneFires on_action MissionFailed(missionId, failedBeatId) (working name); Journal entry mission-failed; parent QuestThread re-evaluates failure cascadeFailedHost/Systems/MissionSystem.cs (proposed)Host/ReadModels/MissionsReadModel.cs (proposed)gd-current-plan.md, gd-journal.md<deferred-to-wave-7>: MissionActiveToFailedTestthrow InvalidStateException("Critical-path beat annotation missing on ResolutionRule for MissionId=<id>")
ActivePlayer explicitly abandons the mission (activity binding <deferred-to-Wave-5a-runtime-backing> per ad-0007 line 56-57)abandonment activity issuednonenoneFires on_action MissionAbandoned(missionId) (working name); Journal entry mission-abandoned; Current Plan retires entryAbandonedHost/Systems/MissionSystem.cs (proposed)Host/ReadModels/MissionsReadModel.cs (proposed)gd-current-plan.md, gd-journal.md<deferred-to-wave-7>: MissionActiveToAbandonedTestthrow InvalidStateException("Abandonment activity not bound for MissionId=<id>")
AvailablePlayer dismisses the mission before accepting (activity binding <deferred-to-Wave-5a-runtime-backing> per ad-0007 line 56-57)dismissal activity issuednonenoneFires on_action MissionAbandoned(missionId) (working name); Journal entry mission-dismissedAbandonedHost/Systems/MissionSystem.cs (proposed)Host/ReadModels/MissionsReadModel.cs (proposed)gd-objectives-screen.md, gd-journal.md<deferred-to-wave-7>: MissionAvailableToAbandonedTestthrow InvalidStateException("Dismissal activity not bound for MissionId=<id>")

Act Progression

Owner doc: gd-act-progression.md. Binding ADR: ../lore/adr/ad-0010-act-progression-actstate-scope.md.

States (per ad-0010): Act 0, Act 1, Act 2, Act 3, Act 4, Act 5, Act 6, Act 7, CampaignEnded. The runtime carrier is the integer ActState.CurrentAct field on the saga-scoped singleton scope ActState; CampaignEnded is reached when Act 7's OnActCompleted fires (CurrentAct is not raised beyond 7).

Per-Act gate predicates are owned by the seven ../acts/gd-act-N-gate-spec.md docs. This table cites the gate-spec doc by name in the trigger_event_or_predicate cell; the predicate text itself lives in the cited doc. No predicate may name a day count or a real-time clock value (ad-0010 lines 96-99).

source_statetriggerrequired_factsconfig_thresholdsblocking_conditionsside_effectstarget_stateruntime_ownerread_modelui_readbacktestsfallback
Act 0core_established == true named-state fact written by the Act 0 mission spine (per gd-act-0-arrival-and-core.md line 163 and ad-0010 lines 87-90)MainCharacter.CoreEstablished == truenone today (gate predicate carries no Config threshold)noneFires on_action OnActCompleted(0) then OnActStarted(1) (working names; promotion deferred to Wave 5a per ad-0010); Journal entry act-transition; ActState updates PreviousAct=0, CurrentAct=1, EnteredOnDay=<currentDay>Act 1Host/Systems/ActProgressionSystem.cs (proposed)Host/ReadModels/ActStateReadModel.cs (proposed; contract-only)gd-core-screen.md saga arc display<deferred-to-wave-7>: ActZeroToOneTestthrow InvalidStateException("MainCharacter.CoreEstablished not true at transition Act 0 -> Act 1")
Act 1Gate predicate authored at ../acts/gd-act-1-gate-spec.mdper the cited gate spec (MainCharacter.CoreEstablished, Core.NamedResidents, Core.ShelterCapacity, Core.WatchAndWardingState, MainCharacter.Camp.HasScoutedAdjacentTerritories)Config.Act1.NamedResidentThreshold, Config.Act1.ScoutedAdjacentThreshold (named in the gate spec; defaults world-size parameterized per Discipline 14)noneFires on_action OnActCompleted(1) then OnActStarted(2) (working names); Journal entry act-transition; ActState updatesAct 2Host/Systems/ActProgressionSystem.cs (proposed)Host/ReadModels/ActStateReadModel.cs (proposed)gd-core-screen.md saga arc display<deferred-to-wave-7>: ActOneToTwoTestthrow InvalidStateException("Gate predicate from gd-act-1-gate-spec.md not satisfied at transition Act 1 -> Act 2")
Act 2Gate predicate authored at ../acts/gd-act-2-gate-spec.mdper the cited gate specper the cited gate specnoneFires on_action OnActCompleted(2) then OnActStarted(3); Journal entry act-transition; ActState updatesAct 3Host/Systems/ActProgressionSystem.cs (proposed)Host/ReadModels/ActStateReadModel.cs (proposed)gd-core-screen.md<deferred-to-wave-7>: ActTwoToThreeTestthrow InvalidStateException("Gate predicate from gd-act-2-gate-spec.md not satisfied at transition Act 2 -> Act 3")
Act 3Gate predicate authored at ../acts/gd-act-3-gate-spec.mdper the cited gate specper the cited gate specnoneFires on_action OnActCompleted(3) then OnActStarted(4); Journal entry act-transition; ActState updatesAct 4Host/Systems/ActProgressionSystem.cs (proposed)Host/ReadModels/ActStateReadModel.cs (proposed)gd-core-screen.md<deferred-to-wave-7>: ActThreeToFourTestthrow InvalidStateException("Gate predicate from gd-act-3-gate-spec.md not satisfied at transition Act 3 -> Act 4")
Act 4Gate predicate authored at ../acts/gd-act-4-gate-spec.mdper the cited gate specper the cited gate specnoneFires on_action OnActCompleted(4) then OnActStarted(5); Journal entry act-transition; ActState updates; Realm.PolityRank re-evaluation invitedAct 5Host/Systems/ActProgressionSystem.cs (proposed)Host/ReadModels/ActStateReadModel.cs (proposed)gd-core-screen.md<deferred-to-wave-7>: ActFourToFiveTestthrow InvalidStateException("Gate predicate from gd-act-4-gate-spec.md not satisfied at transition Act 4 -> Act 5")
Act 5Gate predicate authored at ../acts/gd-act-5-gate-spec.mdper the cited gate specper the cited gate specnoneFires on_action OnActCompleted(5) then OnActStarted(6); Journal entry act-transition; ActState updatesAct 6Host/Systems/ActProgressionSystem.cs (proposed)Host/ReadModels/ActStateReadModel.cs (proposed)gd-core-screen.md<deferred-to-wave-7>: ActFiveToSixTestthrow InvalidStateException("Gate predicate from gd-act-5-gate-spec.md not satisfied at transition Act 5 -> Act 6")
Act 6Gate predicate authored at ../acts/gd-act-6-gate-spec.mdper the cited gate specper the cited gate specnoneFires on_action OnActCompleted(6) then OnActStarted(7); Journal entry act-transition; ActState updatesAct 7Host/Systems/ActProgressionSystem.cs (proposed)Host/ReadModels/ActStateReadModel.cs (proposed)gd-core-screen.md<deferred-to-wave-7>: ActSixToSevenTestthrow InvalidStateException("Gate predicate from gd-act-6-gate-spec.md not satisfied at transition Act 6 -> Act 7")
Act 7Gate predicate authored at ../acts/gd-act-7-gate-spec.md (per ad-0010 lines 101-104)per the cited gate specper the cited gate specnoneFires on_action OnActCompleted(7) then the follow-on CampaignEnded event (per ad-0010 lines 101-104); Journal entry campaign-ended; saga-arc finalizer queued (binding <deferred-to-Wave-5a-runtime-backing>)CampaignEndedHost/Systems/ActProgressionSystem.cs (proposed)Host/ReadModels/ActStateReadModel.cs (proposed)gd-core-screen.md<deferred-to-wave-7>: ActSevenToCampaignEndedTestthrow InvalidStateException("Gate predicate from gd-act-7-gate-spec.md not satisfied at transition Act 7 -> CampaignEnded")

CampaignEnded is terminal. No transition leaves it; the saga-arc finalizer activity (binding <deferred-to-Wave-5a-runtime-backing> per ad-0010 lines 101-104) consumes the state.

Polity Rank

Owner doc: gd-realm-ranks-and-polity.md. Binding ADR: ../lore/adr/ad-0009-realm-scope-and-polity-rank-orthogonality.md.

States (per ad-0009 lines 56-58): Outpost, BannerNetwork, Province, Crown. Forward predicates are authored inline (per resolver decision R13 — ad-0009 lines 76-87 explicitly invited Wave 3a authoring). Regression predicates are deferred to Wave 5a (per ad-0009 line 172).

source_statetriggerrequired_factsconfig_thresholdsblocking_conditionsside_effectstarget_stateruntime_ownerread_modelui_readbacktestsfallback
OutpostRealm.CoveredSettlements.Count >= Config.Realm.BannerNetworkSettlementThreshold && RouteCoverage(Realm) (per ad-0009 lines 76-80; RouteCoverage(Realm) authored inline in gd-realm-ranks-and-polity.md as the connectivity check over the live Route graph filtered to non-Severed Routes)Realm.CoveredSettlements populated; Route graph reachable; no Severed Routes break connectivity for the vertex pairsConfig.Realm.BannerNetworkSettlementThreshold (default 2, world-size parameterized)noneFires on_action PolityRankAdvanced(BannerNetwork) (working name; promotion deferred to Wave 5a per ad-0009 line 172); Journal entry polity-advanced; Settlement screen tier indicator updates; standing-Force authoring unlocksBannerNetworkHost/Systems/RealmRankSystem.cs (proposed)Host/ReadModels/RealmReadModel.cs (proposed; contract-only)Crown-scale strategic map polity overlay; gd-settlement-screen.md tier indicator<deferred-to-wave-7>: PolityOutpostToBannerNetworkTestthrow InvalidStateException("RouteCoverage(Realm) returned false at transition Outpost -> BannerNetwork for RealmId=<id>")
BannerNetworkRealm.Settlements.Any(s => s.SettlementRole == ProvinceCapital) (per ad-0009 lines 81-84)at least one Settlement under the Realm carries SettlementRole == ProvinceCapitalnone today (the predicate is a count threshold of one)noneFires on_action PolityRankAdvanced(Province) (working name); Journal entry polity-advanced; DefenseZone authoring unlocks per ad-0009 lines 89-92; ProvinceDefensePlan capability surfacesProvinceHost/Systems/RealmRankSystem.cs (proposed)Host/ReadModels/RealmReadModel.cs (proposed)Crown-scale strategic map; gd-settlement-screen.md tier indicator<deferred-to-wave-7>: PolityBannerNetworkToProvinceTestthrow InvalidStateException("No Settlement with SettlementRole=ProvinceCapital under RealmId=<id> at transition BannerNetwork -> Province")
ProvinceRealm.Provinces.Count(p => p.HasProvinceCapital) >= Config.Realm.CrownProvinceThreshold (per ad-0009 lines 85-87; default 2, world-size parameterized)at least two Provinces with ProvinceCapitals exist under the RealmConfig.Realm.CrownProvinceThreshold (default 2, world-size parameterized)noneFires on_action PolityRankAdvanced(Crown) (working name); Journal entry polity-advanced; Front authoring unlocks per ad-0009 lines 89-92; multi-Province campaign authority surfacesCrownHost/Systems/RealmRankSystem.cs (proposed)Host/ReadModels/RealmReadModel.cs (proposed)Crown-scale strategic map; gd-settlement-screen.md tier indicator<deferred-to-wave-7>: PolityProvinceToCrownTestthrow InvalidStateException("Province count with ProvinceCapital under threshold at transition Province -> Crown for RealmId=<id>")
BannerNetwork<deferred-to-Wave-5a-runtime-backing> — Realm regression when Route coverage fails (connecting Route enters Severed with no alternative path) or covered-Settlements count falls below threshold; predicate shape deferred per ad-0009 line 172predicate facts deferred<deferred-to-Wave-5a-runtime-backing>noneFires on_action PolityRankRegressed(Outpost) (working name); Journal entry polity-regressed; standing-Force authoring relocksOutpostHost/Systems/RealmRankSystem.cs (proposed)Host/ReadModels/RealmReadModel.cs (proposed)Crown-scale strategic map<deferred-to-wave-7>: PolityBannerNetworkToOutpostTestthrow InvalidStateException("Regression predicate not defined for transition BannerNetwork -> Outpost on RealmId=<id>")
Province<deferred-to-Wave-5a-runtime-backing> — Realm regression on loss of every ProvinceCapital (capital destroyed, demoted, or captured); predicate shape deferred per ad-0009 line 172predicate facts deferred<deferred-to-Wave-5a-runtime-backing>noneFires on_action PolityRankRegressed(BannerNetwork) (working name); Journal entry polity-regressed; DefenseZone authoring relocks; existing committed plans flagged for re-evaluationBannerNetworkHost/Systems/RealmRankSystem.cs (proposed)Host/ReadModels/RealmReadModel.cs (proposed)Crown-scale strategic map<deferred-to-wave-7>: PolityProvinceToBannerNetworkTestthrow InvalidStateException("Regression predicate not defined for transition Province -> BannerNetwork on RealmId=<id>")
Crown<deferred-to-Wave-5a-runtime-backing> — Realm regression on Crown-scale catastrophe (loss of all but one ProvinceCapital); predicate shape deferred per ad-0009 line 172predicate facts deferred<deferred-to-Wave-5a-runtime-backing>noneFires on_action PolityRankRegressed(Province) (working name); Journal entry polity-regressed; Front authoring relocks; existing committed Fronts flagged for re-evaluationProvinceHost/Systems/RealmRankSystem.cs (proposed)Host/ReadModels/RealmReadModel.cs (proposed)Crown-scale strategic map<deferred-to-wave-7>: PolityCrownToProvinceTestthrow InvalidStateException("Regression predicate not defined for transition Crown -> Province on RealmId=<id>")

Front Emergence (ThreatSource and FrontCandidate)

Owner doc: gd-front-emergence.md. Binding ADR: ../lore/adr/ad-0008-pressure-to-front-emergence-scope-state-machine.md.

Two state ladders sit beneath the Front lifecycle. The ThreatSource scope carries a DiscoveryState ladder (per ad-0008 lines 83-85); the FrontCandidate scope carries an implicit lifecycle (per ad-0008 lines 90-100, 112-117). Both ladders are persistent named state, not transient events (ad-0008 Tier 6 line 112-120 is explicit).

ThreatSource DiscoveryState

States (per ad-0008 lines 83-85): Hidden, Suspected, Confirmed, Reported. ThreatSource lifecycle (created at materialization, retired when underlying cause resolves) carries a separate implicit shape; its retirement enum is <deferred-to-Wave-5a-runtime-backing> per ad-0008 lines 198-202.

source_statetriggerrequired_factsconfig_thresholdsblocking_conditionsside_effectstarget_stateruntime_ownerread_modelui_readbacktestsfallback
(no prior row — creation)Province-level pressure channel (ProvinceThreatPressure, ProvinceWardIntegrity, or ProvinceRouteSafety) crosses its committed clamp value under the 6-phase channel pipeline (per ad-0008 lines 70-77; thresholds live as clamp values, not as aggregator constants)per-Province contributory channel resolves above clamp; anchor Territory / Site / Route exists for the Provinceclamp values on the three Province channels (declared in Content/provinces/channels.secs; field names <deferred-to-Wave-5a-runtime-backing> per ad-0008 lines 62-66)noneCreates scope ThreatSource row with Kind, AnchorTerritoryId / AnchorSiteId / AnchorRouteId, DiscoveryState = Hidden, Severity channel attached; fires on_action ThreatSourceMaterialized(threatSourceId) per ad-0008 lines 112-114; Journal entry threat-source-materializedHiddenhost-side; aggregation flows through the 6-phase channel pipeline (no per-tick aggregator system per ad-0008 lines 74-77)Host/ReadModels/ThreatSourcesReadModel.cs (proposed; contract-only)Crown-scale strategic map; gd-territory-dossier.md threat markers<deferred-to-wave-7>: ThreatSourceMaterializedTestthrow InvalidStateException("Province channel clamp not declared at ThreatSource materialization for ProvinceId=<id>")
Hiddenclue or scout observable resolves (<deferred-to-Wave-5a-runtime-backing> — observable predicate per ad-0008 line 83-85)observable predicate reads true<deferred-to-Wave-5a-runtime-backing>noneFires on_action ThreatSourceSuspected(threatSourceId) (working name); Journal entry threat-source-suspected; Territory dossier surfaces hintSuspectedhost-side observable evaluator (proposed; contract-only)Host/ReadModels/ThreatSourcesReadModel.cs (proposed)gd-territory-dossier.md<deferred-to-wave-7>: ThreatSourceHiddenToSuspectedTestthrow InvalidStateException("Observable predicate not defined for ThreatSourceId=<id> at transition Hidden -> Suspected")
Suspectedscout activity or clue chain confirms identity (<deferred-to-Wave-5a-runtime-backing> — confirmation predicate per ad-0008 line 83-85)confirmation predicate reads true<deferred-to-Wave-5a-runtime-backing>noneFires on_action ThreatSourceConfirmed(threatSourceId) (working name); Journal entry threat-source-confirmed; Territory dossier surfaces named threatConfirmedhost-side observable evaluator (proposed)Host/ReadModels/ThreatSourcesReadModel.cs (proposed)gd-territory-dossier.md<deferred-to-wave-7>: ThreatSourceSuspectedToConfirmedTestthrow InvalidStateException("Confirmation predicate not defined for ThreatSourceId=<id> at transition Suspected -> Confirmed")
Confirmednamed reporter (Crown-scale messenger, scout return, Front intelligence) surfaces the threat to the Crown player (<deferred-to-Wave-5a-runtime-backing> — reporting predicate per ad-0008 line 83-85)reporting predicate reads true<deferred-to-Wave-5a-runtime-backing>noneFires on_action ThreatSourceReported(threatSourceId) (working name); Journal entry threat-source-reported; Crown-scale map roster surfaces threatReportedhost-side reporter evaluator (proposed)Host/ReadModels/ThreatSourcesReadModel.cs (proposed)Crown-scale strategic map; gd-territory-dossier.md<deferred-to-wave-7>: ThreatSourceConfirmedToReportedTestthrow InvalidStateException("Reporting predicate not defined for ThreatSourceId=<id> at transition Confirmed -> Reported")
any DiscoveryState<deferred-to-Wave-5a-runtime-backing> — underlying cause resolves (Site cleansed, gate scarring sealed, faction encroachment repulsed, ward-line restored); retirement enum shape deferred per ad-0008 lines 198-202resolution predicate reads true<deferred-to-Wave-5a-runtime-backing>noneRetires scope ThreatSource row; fires on_action ThreatSourceRetired(threatSourceId) (working name); Journal entry threat-source-retired; downstream FrontCandidates re-evaluate aggregationretiredhost-side resolution evaluator (proposed)Host/ReadModels/ThreatSourcesReadModel.cs (proposed)Crown-scale strategic map<deferred-to-wave-7>: ThreatSourceRetiredTestthrow InvalidStateException("Resolution predicate not defined for ThreatSourceId=<id>")

FrontCandidate Lifecycle

The FrontCandidate scope carries an implicit lifecycle (ad-0008 lines 90-100). The states named below are working labels (<deferred-to-Wave-5a-runtime-backing> for promotion to a committed enum). The transitions themselves are committed by ad-0008.

source_statetriggerrequired_factsconfig_thresholdsblocking_conditionsside_effectstarget_stateruntime_ownerread_modelui_readbacktestsfallback
(no prior row — creation)one or more ThreatSources aggregate; AggregatedSeverity channel on the proposed FrontCandidate resolves above its clamp value (per ad-0008 lines 90-100)at least one scope ThreatSource row in Confirmed or Reported exists under the Realm; aggregation pipeline produces AggregatedSeverity above clampclamp value on AggregatedSeverity (declared in Content/fronts/scopes.secs extension; field name <deferred-to-Wave-5a-runtime-backing>)noneCreates scope FrontCandidate row with ProposedHqTerritoryId, CoveredProvinceIds, AggregatedSeverity, ProposedCampaignGoals, DismissedByCrown=0; fires on_action FrontCandidateRaised(candidateId) per ad-0008 lines 114-115; Journal entry front-candidate-raised; Crown-scale map surfaces candidateRaised (working label)host-side; aggregation flows through the 6-phase channel pipeline (no per-tick aggregator per ad-0008 lines 74-77)Host/ReadModels/FrontCandidatesReadModel.cs (proposed; contract-only)Crown-scale strategic map FrontCandidate roster<deferred-to-wave-7>: FrontCandidateRaisedTestthrow InvalidStateException("AggregatedSeverity clamp not declared at FrontCandidate creation")
Raised (working label)Crown player sets DismissedByCrown = 1 via dismissal activity (per ad-0008 lines 90-100; activity binding <deferred-to-Wave-5a-runtime-backing>)Crown-scale dismissal activity issuednonecandidate already elevated to a Front in this tickSets DismissedByCrown = 1; fires on_action FrontCandidateDismissed(candidateId) (working name); Journal entry front-candidate-dismissed; Crown-scale map removes candidate markerDismissed (working label)host-side dismissal handler (proposed)Host/ReadModels/FrontCandidatesReadModel.cs (proposed)Crown-scale strategic map<deferred-to-wave-7>: FrontCandidateDismissedTestthrow InvalidStateException("Dismissal activity not bound for FrontCandidateId=<id>")
Raised (working label)Crown-scale Activity issued by player calls Front.SetStatus(Forming) and binds the FrontCandidate's ThreatSources / ProposedHqTerritoryId / ProposedCampaignGoals into a new Front row (per ad-0008 lines 102-110)Crown-scale elevation activity issued; Realm.PolityRank == Crown per ad-0009 lines 89-92noneRealm.PolityRank != Crown (Front authoring locked); candidate already DismissedCreates scope Front row with Status = Forming (the existing FrontStatus enum per Content/forces/scopes.secs line 222); fires on_action FrontDeclared(frontId, candidateId) per ad-0008 lines 116-117; Journal entry front-declared; FrontCandidate row retired or repurposed (binding <deferred-to-Wave-5a-runtime-backing>)Elevated (working label; the Front itself enters FrontStatus.Forming)host-side elevation handler (proposed)Host/ReadModels/FrontCandidatesReadModel.cs + existing Host/ReadModels/FrontsReadModel.csCrown-scale strategic map; Front roster screen<deferred-to-wave-7>: FrontCandidateElevatedTestthrow InvalidStateException("Elevation activity issued but Realm.PolityRank != Crown for FrontCandidateId=<id>")

Front Lifecycle (existing)

Owner doc: gd-fronts.md. Committed enum: FrontStatus per Content/forces/scopes.secs line 222 (0 Forming, 1 Active, 2 Paused, 3 Concluded, 4 Failed). The Front itself is existing committed surface; this hub binds its transitions into the canonical row format.

source_statetriggerrequired_factsconfig_thresholdsblocking_conditionsside_effectstarget_stateruntime_ownerread_modelui_readbacktestsfallback
(no prior row — creation by Crown elevation)FrontCandidate elevation activity calls Front.SetStatus(Forming) (per ad-0008 lines 102-110 and Content/forces/scopes.secs line 245 method void SetStatus(int status))elevation activity issued from a Raised FrontCandidate; Realm.PolityRank == CrownnonenoneCreates scope Front row with Status = Forming; fires on_action FrontDeclared(frontId, candidateId) per ad-0008 line 116-117; Journal entry front-declaredFormingexisting Host/Data/FrontData.cs (no new system needed for lifecycle alone)existing Host/ReadModels/FrontsReadModel.cs (projects nothing today per gd-forces-phase-b-design-questions.md line 492; populated in Wave 5a)Crown-scale strategic map; Front roster screen<deferred-to-wave-7>: FrontFormingCreationTestthrow InvalidStateException("Realm.PolityRank != Crown at Front creation")
Forming<deferred-to-Wave-5a-runtime-backing> — Crown player commits Province plans and Forces per gd-fronts.md line 181 (concrete trigger predicate deferred per ad-0009 line 172 working-name promotion deferral and per ad-0008 line 102-110 activity binding deferral)committed ProvinceDefensePlan rows reference the Front; at least one Force assigned to a Front objective<deferred-to-Wave-5a-runtime-backing>no Force assignedSets Front.Status = Active via SetStatus(Active); fires on_action FrontActivated(frontId) (working name; promotion deferred to Wave 5a); Journal entry front-activatedActiveexisting Host/Data/FrontData.csHost/ReadModels/FrontsReadModel.csCrown-scale strategic map; Front roster screen<deferred-to-wave-7>: FrontFormingToActiveTestthrow InvalidStateException("No Force assigned to FrontId=<id> at transition Forming -> Active")
ActiveCrown player explicit SetStatus(Paused) activity (per Content/forces/scopes.secs line 245)Crown-scale pause activity issuednonenoneSets Front.Status = Paused; fires on_action FrontPaused(frontId) (working name); Journal entry front-paused; Forces under the Front halt advancePausedexisting Host/Data/FrontData.csHost/ReadModels/FrontsReadModel.csFront roster screen<deferred-to-wave-7>: FrontActiveToPausedTestthrow InvalidStateException("Pause activity not bound for FrontId=<id>")
PausedCrown player explicit SetStatus(Active) activityCrown-scale resume activity issuednonenoneSets Front.Status = Active; fires on_action FrontResumed(frontId) (working name); Journal entry front-resumed; Forces resume advanceActiveexisting Host/Data/FrontData.csHost/ReadModels/FrontsReadModel.csFront roster screen<deferred-to-wave-7>: FrontPausedToActiveTestthrow InvalidStateException("Resume activity not bound for FrontId=<id>")
ActiveCrown player explicit SetStatus(Concluded) activity (per gd-fronts.md lines 192-195 — a Front is not auto-concluded; the Crown player must acknowledge conclusion explicitly)Crown-scale conclusion activity issued; player acknowledges Campaign Goals metnonenoneSets Front.Status = Concluded; fires on_action FrontConcluded(frontId) (working name); Journal entry front-concluded; Forces released; existing on_action FrontConcluded surfaces in Saga recapConcludedexisting Host/Data/FrontData.csHost/ReadModels/FrontsReadModel.csFront roster screen; Saga recap<deferred-to-wave-7>: FrontActiveToConcludedTestthrow InvalidStateException("Conclusion activity not bound for FrontId=<id>")
ActiveCrown player explicit SetStatus(Failed) activity (per gd-fronts.md lines 192-195 — failure is acknowledged explicitly)Crown-scale failure activity issued; player acknowledges Campaign Goals unreachablenonenoneSets Front.Status = Failed; fires on_action FrontFailed(frontId) (working name); Journal entry front-failed; Forces released; Saga recap records failureFailedexisting Host/Data/FrontData.csHost/ReadModels/FrontsReadModel.csFront roster screen; Saga recap<deferred-to-wave-7>: FrontActiveToFailedTestthrow InvalidStateException("Failure activity not bound for FrontId=<id>")

ProvinceDefensePlan PlanState

Owner doc: gd-defense-zones.md (the ## PlanState section authored by Foundation Hardening). Authoring authority: Foundation Hardening resolver decision R4 (2026-05-17). No separate ADR file backs this enum because the state machine itself is the authoritative record; the authoring routing decision is recorded in the owner doc.

States (per gd-defense-zones.md ## PlanState): Unplanned (0), Drafting (1), Committed (2), Failed (3), Recovered (4). Every Province with polity ownership carries a ProvinceDefensePlan row from the moment the Province is created; Unplanned is the explicit starting state, not the absence of a row.

source_statetriggerrequired_factsconfig_thresholdsblocking_conditionsside_effectstarget_stateruntime_ownerread_modelui_readbacktestsfallback
UnplannedCrown opens the plan UI for the Province (activity binding <deferred-to-Wave-5a-runtime-backing>)Crown-scale draft activity issued; Realm.PolityRank >= Province per ad-0009 lines 89-92noneRealm.PolityRank < Province (DefenseZone authoring locked)Sets PlanState = Drafting; fires on_action PlanDrafting(planId) (working name; promotion deferred to Wave 5a); Journal entry plan-draftingDraftingHost/Systems/ProvinceDefensePlanSystem.cs (proposed)existing Host/ReadModels/ProvinceDefensePlansReadModel.cs (projects nothing today; populated in Wave 5a)gd-territory-dossier.md; Defense Zone screen (<screen not yet authored>)<deferred-to-wave-7>: PlanUnplannedToDraftingTestthrow InvalidStateException("Realm.PolityRank < Province at transition Unplanned -> Drafting for ProvinceId=<id>")
DraftingCrown finalizes the plan via Commit activity (activity binding <deferred-to-Wave-5a-runtime-backing>)at least one DefenseZone with valid Posture and assigned Force exists on the plan (per gd-defense-zones.md ## PlanState)noneno DefenseZone with valid Posture and Force assignmentSets PlanState = Committed; fires on_action PlanCommitted(planId) (working name); Journal entry plan-committed; tick pipeline honors Force assignments; DefaultDoctrine and DefaultReservePercent take effectCommittedHost/Systems/ProvinceDefensePlanSystem.cs (proposed)Host/ReadModels/ProvinceDefensePlansReadModel.csgd-territory-dossier.md; Defense Zone screen<deferred-to-wave-7>: PlanDraftingToCommittedTestthrow InvalidStateException("Commit refused: no DefenseZone with valid Posture and Force assignment for PlanId=<id>")
CommittedDefenseZone failure cascades to plan-level failure (per gd-defense-zones.md ## Zone Failure And Recovery; cascade predicate <deferred-to-Wave-5a-runtime-backing>)a DefenseZone under the plan has failed (Capital zone lost, supply Route severed beyond endurance, or equivalent committed failure mode)<deferred-to-Wave-5a-runtime-backing>noneSets PlanState = Failed; fires on_action PlanFailed(planId, failedZoneId) (working name); Journal entry plan-failed; Forces under the plan released or relocated; Current Plan urgency promotesFailedHost/Systems/ProvinceDefensePlanSystem.cs (proposed)Host/ReadModels/ProvinceDefensePlansReadModel.csgd-territory-dossier.md<deferred-to-wave-7>: PlanCommittedToFailedTestthrow InvalidStateException("Failure cascade predicate not defined for PlanId=<id>")
FailedCrown re-commits the plan via Recommit activity (activity binding <deferred-to-Wave-5a-runtime-backing>)Crown-scale recommit activity issued; at least one DefenseZone with valid Posture and Force assignment existsnoneno DefenseZone with valid Posture and Force assignmentSets PlanState = Recovered; fires on_action PlanRecovered(planId) (working name); Journal entry plan-recovered; tick pipeline resumes honoring Force assignmentsRecoveredHost/Systems/ProvinceDefensePlanSystem.cs (proposed)Host/ReadModels/ProvinceDefensePlansReadModel.csgd-territory-dossier.md<deferred-to-wave-7>: PlanFailedToRecoveredTestthrow InvalidStateException("Recommit refused: no DefenseZone with valid Posture and Force assignment for PlanId=<id>")
Recoveredhysteresis window elapses with no new DefenseZone failures (hysteresis predicate <deferred-to-Wave-5a-runtime-backing>; the design intent is that Recovered collapses back to Committed after stability, so the historical record can distinguish first-commit from recovery while the running state matches normal Committed)no DefenseZone failure during the hysteresis windowConfig.ProvinceDefensePlan.RecoveryHysteresisWindow (named; default world-size parameterized; <deferred-to-Wave-5a-runtime-backing>)a DefenseZone failure occurred during the windowSets PlanState = Committed; fires on_action PlanRecoveryComplete(planId) (working name); Journal entry plan-recovery-completeCommittedHost/Systems/ProvinceDefensePlanSystem.cs (proposed)Host/ReadModels/ProvinceDefensePlansReadModel.csgd-territory-dossier.md<deferred-to-wave-7>: PlanRecoveredToCommittedTestthrow InvalidStateException("Hysteresis window not defined for PlanId=<id> at transition Recovered -> Committed")

Force Lifecycle (existing)

Owner doc: gd-forces.md. Committed enum: LifecycleState per Content/forces/scopes.secs lines 58-60 (0 Forming, 1 Available, 2 Operating, 3 Returning, 4 Disbanding, 5 Lost). The existing doc enumerates lifecycle states without committing every transition predicate inline; predicate cells <deferred-to-Wave-5a-runtime-backing> where the existing doc is silent.

source_statetriggerrequired_factsconfig_thresholdsblocking_conditionsside_effectstarget_stateruntime_ownerread_modelui_readbacktestsfallback
Forming<deferred-to-Wave-5a-runtime-backing> — recruitment / equipment fill reaches Strength >= MinStrengthThreshold per gd-forces.md lines 259-261; threshold name <deferred-to-Wave-5a-runtime-backing>Strength channel above threshold; detachment roster populatedConfig.Force.MinStrengthThreshold (named; <deferred-to-Wave-5a-runtime-backing>)noneFires on_action ForceAvailable(forceId) (working name); Journal entry force-available; Force becomes assignable to OperationsAvailableexisting Host/Data/ForceData.cs (proposed system <deferred-to-Wave-5a-runtime-backing>)existing Host/ReadModels/ForcesReadModel.cs (projects nothing today; populated in Wave 5a)Force order-of-battle screen (<screen not yet authored>)<deferred-to-wave-7>: ForceFormingToAvailableTestthrow InvalidStateException("MinStrengthThreshold not declared for ForceId=<id>")
AvailableOperation begin activity fires (Operation.Begin() method per Content/forces/scopes.secs line 134 and gd-forces.md line 262)a child Operation row enters Status = ActivenoneForce already Operating on a different Operation (single-Operation-per-Force constraint)Fires on_action ForceOperating(forceId, operationId) (working name); Journal entry force-operatingOperatingexisting Host/Data/ForceData.csHost/ReadModels/ForcesReadModel.csForce order-of-battle screen<deferred-to-wave-7>: ForceAvailableToOperatingTestthrow InvalidStateException("Operation.Begin() called but Operation not in Pending for ForceId=<id>")
OperatingOperation reaches Status = Completed / Cancelled / Failed and the Force enters return movement (<deferred-to-Wave-5a-runtime-backing> — return predicate per gd-forces.md line 269-270)child Operation row reaches terminal StatusnonenoneFires on_action ForceReturning(forceId) (working name); Journal entry force-returningReturningexisting Host/Data/ForceData.csHost/ReadModels/ForcesReadModel.csForce order-of-battle screen<deferred-to-wave-7>: ForceOperatingToReturningTestthrow InvalidStateException("Return predicate not defined for ForceId=<id>")
ReturningForce reaches home Territory or depot (<deferred-to-Wave-5a-runtime-backing> — arrival predicate per gd-forces.md line 269-270)Force HomeTerritoryId reached or depot anchor satisfiednonenoneFires on_action ForceAvailable(forceId) (working name); Journal entry force-availableAvailableexisting Host/Data/ForceData.csHost/ReadModels/ForcesReadModel.csForce order-of-battle screen<deferred-to-wave-7>: ForceReturningToAvailableTestthrow InvalidStateException("Arrival predicate not defined for ForceId=<id>")
Available or ReturningCrown player issues disband activity (activity binding <deferred-to-Wave-5a-runtime-backing>; gd-forces.md line 269-270 notes disband is reversible only until detachments absorbed elsewhere)Crown-scale disband activity issuednoneForce Operating (cannot disband mid-Operation)Sets LifecycleState = Disbanding; fires on_action ForceDisbanding(forceId) (working name); Journal entry force-disbanding; detachments queued for absorptionDisbandingexisting Host/Data/ForceData.csHost/ReadModels/ForcesReadModel.csForce order-of-battle screen<deferred-to-wave-7>: ForceToDisbandingTestthrow InvalidStateException("Disband activity issued while Force Operating for ForceId=<id>")
Disbandingdetachments absorbed elsewhere or released (<deferred-to-Wave-5a-runtime-backing> — absorption predicate per gd-forces.md line 269-270)every detachment under the Force absorbed by another Force or releasednoneany detachment still in IsCommitted = 1Retires scope Force row; fires on_action ForceRetired(forceId) (working name); Journal entry force-retiredretiredexisting Host/Data/ForceData.csHost/ReadModels/ForcesReadModel.csForce order-of-battle screen<deferred-to-wave-7>: ForceDisbandingToRetiredTestthrow InvalidStateException("Absorption predicate not defined for ForceId=<id>")
any non-LostForce destroyed in combat or by catastrophic supply collapse (<deferred-to-Wave-5a-runtime-backing> — loss predicate per gd-forces.md line 269-270; explicit per the doc: a Lost Force cannot be recovered)loss predicate reads truenonenoneSets LifecycleState = Lost; fires on_action ForceLost(forceId) (working name); Journal entry force-lost; Operations under the Force fail; Saga recap records lossLostexisting Host/Data/ForceData.csHost/ReadModels/ForcesReadModel.csForce order-of-battle screen; Saga recap<deferred-to-wave-7>: ForceToLostTestthrow InvalidStateException("Loss predicate not defined for ForceId=<id>")

Operation Lifecycle (existing)

Owner doc: gd-operations.md. Two committed axes per Content/forces/scopes.secs lines 109-114: Phase (7 values Scouting / Securing / Staging / Screening / Main / Consolidation / Withdrawal, per lines 108-109) and Status (5 values Pending / Active / Completed / Cancelled / Failed, per lines 110-112). Operations advance both axes; the canonical phase descriptions live at gd-operations.md lines 141-153. Per-phase predicates are deferred to Wave 5a per resolver R13 (the existing doc enumerates phases but does not commit every advance predicate).

Operation Phase

source_statetriggerrequired_factsconfig_thresholdsblocking_conditionsside_effectstarget_stateruntime_ownerread_modelui_readbacktestsfallback
ScoutingOperation.AdvancePhase() per Content/forces/scopes.secs line 133; per-phase predicate <deferred-to-Wave-5a-runtime-backing> per gd-operations.md lines 141-143scouting beat resolved per the decomposition pattern<deferred-to-Wave-5a-runtime-backing>scouting beat unresolvedFires on_action OperationPhaseAdvanced(operationId, Securing) (working name); Journal entry operation-phaseSecuringexisting Host/Data/OperationData.cs (proposed system <deferred-to-Wave-5a-runtime-backing>)existing Host/ReadModels/OperationsReadModel.cs (projects nothing today; populated in Wave 5a)strategic map plan-icon roster; Operation 7-phase drill-in<deferred-to-wave-7>: OperationScoutingToSecuringTestthrow InvalidStateException("Advance predicate not defined for Scouting -> Securing on OperationId=<id>")
SecuringOperation.AdvancePhase(); per-phase predicate <deferred-to-Wave-5a-runtime-backing>securing beat resolved<deferred-to-Wave-5a-runtime-backing>securing beat unresolvedFires on_action OperationPhaseAdvanced(operationId, Staging); Journal entry operation-phaseStagingexisting Host/Data/OperationData.csHost/ReadModels/OperationsReadModel.csstrategic map; Operation drill-in<deferred-to-wave-7>: OperationSecuringToStagingTestthrow InvalidStateException("Advance predicate not defined for Securing -> Staging on OperationId=<id>")
StagingOperation.AdvancePhase(); per-phase predicate <deferred-to-Wave-5a-runtime-backing>staging beat resolved<deferred-to-Wave-5a-runtime-backing>staging beat unresolvedFires on_action OperationPhaseAdvanced(operationId, Screening); Journal entry operation-phaseScreeningexisting Host/Data/OperationData.csHost/ReadModels/OperationsReadModel.csstrategic map; Operation drill-in<deferred-to-wave-7>: OperationStagingToScreeningTestthrow InvalidStateException("Advance predicate not defined for Staging -> Screening on OperationId=<id>")
ScreeningOperation.AdvancePhase(); per-phase predicate <deferred-to-Wave-5a-runtime-backing>screening beat resolved<deferred-to-Wave-5a-runtime-backing>screening beat unresolvedFires on_action OperationPhaseAdvanced(operationId, Main); Journal entry operation-phaseMainexisting Host/Data/OperationData.csHost/ReadModels/OperationsReadModel.csstrategic map; Operation drill-in<deferred-to-wave-7>: OperationScreeningToMainTestthrow InvalidStateException("Advance predicate not defined for Screening -> Main on OperationId=<id>")
MainOperation.AdvancePhase(); per-phase predicate <deferred-to-Wave-5a-runtime-backing>main beat resolved<deferred-to-Wave-5a-runtime-backing>main beat unresolvedFires on_action OperationPhaseAdvanced(operationId, Consolidation); Journal entry operation-phaseConsolidationexisting Host/Data/OperationData.csHost/ReadModels/OperationsReadModel.csstrategic map; Operation drill-in<deferred-to-wave-7>: OperationMainToConsolidationTestthrow InvalidStateException("Advance predicate not defined for Main -> Consolidation on OperationId=<id>")
ConsolidationOperation.AdvancePhase(); per-phase predicate <deferred-to-Wave-5a-runtime-backing>consolidation beat resolved<deferred-to-Wave-5a-runtime-backing>consolidation beat unresolvedFires on_action OperationPhaseAdvanced(operationId, Withdrawal); Journal entry operation-phaseWithdrawalexisting Host/Data/OperationData.csHost/ReadModels/OperationsReadModel.csstrategic map; Operation drill-in<deferred-to-wave-7>: OperationConsolidationToWithdrawalTestthrow InvalidStateException("Advance predicate not defined for Consolidation -> Withdrawal on OperationId=<id>")
WithdrawalOperation.AdvancePhase() followed by Status transition to Completed; per-phase predicate <deferred-to-Wave-5a-runtime-backing>withdrawal beat resolved; Force returned home or arrived at depot<deferred-to-Wave-5a-runtime-backing>withdrawal beat unresolvedSets Status = Completed; fires on_action OperationCompleted(operationId) (working name); Journal entry operation-completed; parent Force enters Returning evaluationWithdrawal (terminal Phase; Status moves to Completed)existing Host/Data/OperationData.csHost/ReadModels/OperationsReadModel.csstrategic map; Operation drill-in<deferred-to-wave-7>: OperationWithdrawalToCompletedTestthrow InvalidStateException("Withdrawal completion predicate not defined for OperationId=<id>")

Operation Status

source_statetriggerrequired_factsconfig_thresholdsblocking_conditionsside_effectstarget_stateruntime_ownerread_modelui_readbacktestsfallback
PendingOperation.Begin() per Content/forces/scopes.secs line 134; required scouting precondition met per gd-operations.md line 141scouting beat preconditions met; parent Force in Availablenoneparent Force in Operating, Forming, Disbanding, or LostSets Status = Active; fires on_action OperationActivated(operationId) (working name); Journal entry operation-active; parent Force enters OperatingActiveexisting Host/Data/OperationData.csHost/ReadModels/OperationsReadModel.csstrategic map; Operation drill-in<deferred-to-wave-7>: OperationPendingToActiveTestthrow InvalidStateException("Operation.Begin() refused: parent Force not Available for OperationId=<id>")
ActiveWithdrawal Phase resolves and Operation completion predicate reads true (<deferred-to-Wave-5a-runtime-backing> per gd-operations.md lines 141-153)Phase reaches Withdrawal; completion predicate reads true<deferred-to-Wave-5a-runtime-backing>Phase not WithdrawalSets Status = Completed; fires on_action OperationCompleted(operationId); Journal entry operation-completedCompletedexisting Host/Data/OperationData.csHost/ReadModels/OperationsReadModel.csstrategic map<deferred-to-wave-7>: OperationActiveToCompletedTestthrow InvalidStateException("Completion predicate not defined for OperationId=<id>")
ActiveCrown player issues Operation.Cancel() per Content/forces/scopes.secs line 135Crown-scale cancel activity issuednonenoneSets Status = Cancelled; fires on_action OperationCancelled(operationId) (working name); Journal entry operation-cancelled; parent Force enters ReturningCancelledexisting Host/Data/OperationData.csHost/ReadModels/OperationsReadModel.csstrategic map<deferred-to-wave-7>: OperationActiveToCancelledTestthrow InvalidStateException("Cancel activity not bound for OperationId=<id>")
ActiveOperation failure predicate reads true (<deferred-to-Wave-5a-runtime-backing> per gd-operations.md lines 141-153; e.g., target lost, supply collapse, Force loss)failure predicate reads true<deferred-to-Wave-5a-runtime-backing>noneSets Status = Failed; fires on_action OperationFailed(operationId) (working name); Journal entry operation-failed; parent Force enters Returning or LostFailedexisting Host/Data/OperationData.csHost/ReadModels/OperationsReadModel.csstrategic map<deferred-to-wave-7>: OperationActiveToFailedTestthrow InvalidStateException("Failure predicate not defined for OperationId=<id>")

Feature / Territory / Site State Ladders (cite-only)

Three committed state-axis enums live in the Generated tree and are owned by gd-state-axes-and-thresholds.md. This hub does NOT redefine them; transition cells reference the generation-contract docs that author the read-model-side transitions.

  • FeatureDiscoveryState (9 states per Generated/Territories/FeaturePlacement/FeaturePlacementTypes.cs and gd-state-axes-and-thresholds.md lines 42-57): Hidden, Hinted, Discovered, Surveyed, Handled, Secured, Developed, Depleted, Destroyed.
  • TerritoryKnowledgeState (8 states per Generated/Territories/TerritoryKnowledgeTypes.cs and gd-state-axes-and-thresholds.md lines 110-127).
  • TerritoryContentState (10 states per Generated/Territories/FeaturePlacement/FeatureContentTypes.cs and gd-state-axes-and-thresholds.md lines 153-164).

Transition cells: <authored in [gd-feature-generation-contract.md](/secs/valenar/generation/gd-feature-generation-contract/) / [gd-territory-generation-contract.md](/secs/valenar/generation/gd-territory-generation-contract/) — read-model-side, not authored here>. The transitions belong to the discovery / knowledge / content pipelines owned by the feature- and territory-generation contracts. Future Wave 5a (or a separate read-model-side wave) authors the transition-row table for those three ladders against the same 12-column schema; this hub will link those tables in place of the cite-only notice.

MC Condition Ladders

Owner doc: gd-character-conditions-and-injuries.md. Binding ADR: ../lore/adr/ad-0012-character-conditions-and-injuries-as-modifiers.md.

Conditions are Modifier-attached, not scope-state-channel (per ad-0012 lines 111-126 and ad-0013 lines 111-124 — conditions are not an instantiation of the scope + state-channel + template-archetype pattern). The transitions are Modifier apply / decay / clear events, not scope-state transitions. The ladder rungs are authoritative text per ad-0012 lines 102-110, not a runtime enum.

Seven conditions per ad-0012 lines 113-126 and gd-character-conditions-and-injuries.md condition catalog. Each condition's rung labels are owned by the owner doc and cited here rather than re-authored:

ConditionRung Ladder (owner doc)
Hungrywell-fed -> satisfied -> peckish -> hungry -> starving per gd-character-conditions-and-injuries.md (ad-0012 lines 46-48 seed)
Coldwarm -> cool -> chilly -> cold -> freezing per gd-character-conditions-and-injuries.md
Exhaustedrested -> tired -> weary -> exhausted -> spent per gd-character-conditions-and-injuries.md
Afraidcomposed -> wary -> afraid -> terrified -> broken per gd-character-conditions-and-injuries.md
Bleedingclean -> grazed -> bleeding -> hemorrhaging -> exsanguinating per gd-character-conditions-and-injuries.md
Taintedpure -> exposed -> tainted -> corrupted -> consumed per gd-character-conditions-and-injuries.md
Poisonedunaffected -> afflicted -> poisoned -> toxic -> dying per gd-character-conditions-and-injuries.md

Each ladder runs better-to-worse. The leftmost rung is the absence of the condition; the rightmost rung is the terminal state. The transitions are Modifier-attached, with the apply / tend / clear binding rules owned by gd-character-conditions-and-injuries.md.

source_statetriggerrequired_factsconfig_thresholdsblocking_conditionsside_effectstarget_stateruntime_ownerread_modelui_readbacktestsfallback
any rung (excluding the worst rung)Worsening pressure source applies a step of the Modifier (apply binding <deferred-to-Wave-5a-runtime-backing> per ad-0012 lines 187-194)apply predicate from gd-character-conditions-and-injuries.md reads true (e.g., subsistence absent for Hungry; environmental cold exposure for Cold; demonic taint contact for Tainted)per-condition Config field <deferred-to-Wave-5a-runtime-backing> per ad-0012 lines 102-110 (the exact rung thresholds are a balance pass)terminal rung already attainedApplies the next-worse ladder-rung Modifier on the named character scope; fires on_action ConditionApplied(characterId, conditionId, newRung) (working name; promotion deferred to Wave 5a); Journal entry condition-applied; Character screen condition strip updatesnext-worse rungexisting ModifierBindingStore in src/SECS.Engine/ plus apply / tend / clear Activities authored in Wave 5acharacter info read-model reads Modifier presence on character scopegd-character-screen.md per ad-0012 lines 200-205<deferred-to-wave-7>: ConditionWorsenTestthrow InvalidStateException("Apply predicate not defined for ConditionId=<id> on CharacterId=<id>")
any rung (excluding the best rung)Tending or recovery binding clears one step (tend / clear binding <deferred-to-Wave-5a-runtime-backing> per ad-0012 lines 187-194)recovery predicate reads true (e.g., eating returns Hungry toward well-fed; warming Items / shelter returns Cold toward warm; ward-stone or ritual returns Tainted toward pure)per-condition Config field <deferred-to-Wave-5a-runtime-backing>best rung already attainedClears the current ladder-rung Modifier and applies the next-better rung Modifier (or clears entirely if at second-best rung); fires on_action ConditionImproved(characterId, conditionId, newRung) (working name); Journal entry condition-improved; Character screen condition strip updatesnext-better rungexisting ModifierBindingStore plus tend / clear Activities authored in Wave 5acharacter info read-modelgd-character-screen.md<deferred-to-wave-7>: ConditionImproveTestthrow InvalidStateException("Recovery predicate not defined for ConditionId=<id> on CharacterId=<id>")
terminal rungSustained terminal exposure resolves with an acquired-trait conversion (per ad-0012 lines 95-104 and gd-character-conditions-and-injuries.md ## Acquired Trait Conversion)terminal-rung Modifier remains applied for the conversion duration (<deferred-to-Wave-5a-runtime-backing>); conversion predicate reads trueConfig.Condition.<Name>.TerminalConversionDuration (named; <deferred-to-Wave-5a-runtime-backing>)character died from the terminal condition before conversionApplies the acquired trait per the per-condition conversion target rule (per ad-0012 lines 95-104); fires on_action ConditionConverted(characterId, conditionId, traitId) (working name); Journal entry condition-convertedterminal rung cleared; acquired trait appliedexisting ModifierBindingStore plus conversion Activity authored in Wave 5acharacter info read-modelgd-character-screen.md<deferred-to-wave-7>: ConditionTerminalConversionTestthrow InvalidStateException("Terminal conversion predicate not defined for ConditionId=<id> on CharacterId=<id>")

Injuries are single-rung Modifiers (per gd-character-conditions-and-injuries.md ## Injury Catalog); they do not carry a per-injury rung ladder. Injury apply / heal transitions follow the same Modifier-attached shape as the condition rung-step transitions above; the per-injury binding rules are owned by gd-character-conditions-and-injuries.md and are not re-tabled here.

Glossary Propagation

The following terms are introduced or aggregated by this hub. Wave 3c (canonical propagation) propagates them to gd-glossary.md, gd-canon.md, and README.md per .claude/rules/valenar-contract-backing.md § Canonical Glossary Targets.

  • Transition row — a single row in any state-machine table on this page; carries the 12-column schema defined above.
  • <deferred-to-Wave-5a-runtime-backing> — the canonical marker for a transition cell whose authoritative predicate or threshold field is deferred to Wave 5a runtime-backing closure. The marker is greppable: a flat list of every cell carrying it is the open follow-on list below.
  • <deferred-to-wave-7> — the canonical marker for the tests_required column. Each test name is greppable so the Wave 7 test-authoring wave can produce a flat list of every test row this hub generates.
  • <screen not yet authored> — the canonical marker for a UI read-back surface that does not yet have a committed ux/gd-*.md owner doc.
  • (working name) — the canonical annotation for an on_action label whose promotion to committed metadata is deferred to Wave 5a.
  • Source state / target state / trigger / required facts / config thresholds / blocking conditions / side effects / runtime owner / read model surface / ui readback / tests required / fallback behavior — the 12 column names of the canonical transition row schema.
  • PlanState — the new ProvinceDefensePlan.PlanState enum (Foundation Hardening resolver decision R4 on 2026-05-17); owned by gd-defense-zones.md.
  • DiscoveryState — the ThreatSource.DiscoveryState enum per ad-0008 lines 83-85.
  • Quest Thread Lifecycle / Mission Lifecycle / Polity Rank / Act Progression / Front Emergence — names of the five new state-machine families introduced by Foundation Hardening.

Cross-References

State-machine owner docs (the per-doc detail surfaces):

Per-Act gate specifications (each owns its own Act N -> N+1 predicate):

Existing precedent docs (cited, not redefined here):

Planning vocabulary and pressure model:

UX read-back surfaces:

ADRs:

Quest threads index and lanes:

Rules and authoring authority:

Open Follow-On Items

Aggregated registry of every <deferred-to-Wave-5a-runtime-backing> cell in the tables above. Wave 5a authors consume this list to plan the runtime-backing work. One bullet per deferred predicate / threshold / binding; the cited owner doc and ADR identify the authoritative source.

Quest Thread Lifecycle:

  • QuestThread Active -> Suspended: precondition violation predicate. Design intent: a thread suspends when its template's SuspensionPredicate reads true; predicate shape deferred per ad-0007 lines 56-57.
  • QuestThread Suspended -> Active: precondition restored predicate; symmetric to the above; deferred per ad-0007 lines 56-57.
  • QuestThread Active -> Resolved: ResolutionRule predicate against required child Missions; deferred per ad-0007 lines 56-57.
  • QuestThread Active -> Failed: critical-path failure predicate against child Missions; deferred per ad-0007 lines 56-57.
  • QuestThread Active -> Abandoned and Available -> Abandoned: abandonment / dismissal activity bindings; deferred per ad-0007 lines 56-57.

Mission Lifecycle:

  • Mission Pending -> Available: precondition predicate authored on template<Mission>.PreconditionPredicate; deferred per ad-0007 lines 56-57.
  • Mission Available -> Accepted: acceptance activity binding; deferred per ad-0007 lines 56-57.
  • Mission Active -> Resolved: ResolutionRule predicate against required activity beats; deferred per ad-0007 lines 56-57.
  • Mission Active -> Failed: critical-path beat failure predicate; deferred per ad-0007 lines 56-57.
  • Mission Active -> Abandoned and Available -> Abandoned: abandonment / dismissal activity bindings; deferred per ad-0007 lines 56-57.

Act Progression:

  • Act 7 -> CampaignEnded saga-arc finalizer activity binding; deferred per ad-0010 lines 101-104.
  • Working-name promotion for every on_action OnActCompleted(N) and OnActStarted(N+1) label; deferred per ad-0010.

Polity Rank:

  • PolityRank BannerNetwork -> Outpost regression predicate; design intent: Realm regression when Route coverage fails (Severed Route with no alternative path) or covered-Settlements count falls below threshold; deferred per ad-0009 line 172.
  • PolityRank Province -> BannerNetwork regression predicate; design intent: Realm regression on loss of every ProvinceCapital (capital destroyed, demoted, or captured); deferred per ad-0009 line 172.
  • PolityRank Crown -> Province regression predicate; design intent: Realm regression on Crown-scale catastrophe (loss of all but one ProvinceCapital); deferred per ad-0009 line 172.
  • Working-name promotion for on_action PolityRankAdvanced and PolityRankRegressed labels; deferred per ad-0009 line 172.

Front Emergence (ThreatSource and FrontCandidate):

  • Territory-level input channel field names (gate-activity, ward-failure, route-safety); deferred per ad-0008 lines 62-66.
  • Province-level aggregation channel field names (ProvinceThreatPressure, ProvinceWardIntegrity, ProvinceRouteSafety); deferred per ad-0008 lines 62-66.
  • ThreatSource.Kind enum value list; deferred per ad-0008 lines 198-202.
  • Severity channel's exact 6-phase composition (clamp values, multiplicative contributions); deferred per ad-0008 lines 198-202.
  • ThreatSource retirement enum shape; deferred per ad-0008 lines 198-202.
  • ThreatSource Hidden -> Suspected observable predicate; deferred per ad-0008 lines 83-85.
  • ThreatSource Suspected -> Confirmed confirmation predicate; deferred per ad-0008 lines 83-85.
  • ThreatSource Confirmed -> Reported reporting predicate; deferred per ad-0008 lines 83-85.
  • FrontCandidate working state-label enum (Raised, Active, Dismissed, Elevated) promotion to a committed enum; deferred per ad-0008 lines 90-100.
  • FrontCandidate AggregatedSeverity clamp value; deferred per ad-0008 lines 90-100.
  • FrontCandidate dismissal and elevation activity bindings; deferred per ad-0008 lines 90-110.

Front Lifecycle (existing):

  • Front Forming -> Active predicate: Crown player commits Province plans and Forces per gd-fronts.md line 181; predicate shape deferred per ad-0009 working-name promotion and per ad-0008 activity-binding deferral.
  • Working-name promotion for on_action FrontActivated, FrontPaused, FrontResumed, FrontConcluded, FrontFailed labels; deferred per ad-0008.

ProvinceDefensePlan PlanState:

  • PlanState Committed -> Failed cascade predicate from DefenseZone failure modes (Capital zone lost, supply Route severed beyond endurance, or equivalent); deferred per gd-defense-zones.md ## Zone Failure And Recovery.
  • PlanState Recovered -> Committed hysteresis window (Config.ProvinceDefensePlan.RecoveryHysteresisWindow); deferred per gd-defense-zones.md ## PlanState.
  • Plan draft / commit / recommit activity bindings; deferred per gd-defense-zones.md.
  • Working-name promotion for on_action PlanDrafting, PlanCommitted, PlanFailed, PlanRecovered, PlanRecoveryComplete labels; deferred per Foundation Hardening resolver R4 (2026-05-17).

Force Lifecycle (existing):

  • Force Forming -> Available MinStrengthThreshold Config field name; deferred per gd-forces.md lines 259-261.
  • Force Operating -> Returning return predicate; deferred per gd-forces.md line 269-270.
  • Force Returning -> Available arrival predicate; deferred per gd-forces.md line 269-270.
  • Force Disbanding -> retired absorption predicate; deferred per gd-forces.md line 269-270.
  • Force any-state -> Lost loss predicate; deferred per gd-forces.md line 269-270.
  • Working-name promotion for on_action ForceAvailable, ForceOperating, ForceReturning, ForceDisbanding, ForceLost, ForceRetired labels; deferred to Wave 5a.

Operation Lifecycle (existing):

  • Per-phase advance predicates for all six Phase transitions (Scouting -> Securing, Securing -> Staging, Staging -> Screening, Screening -> Main, Main -> Consolidation, Consolidation -> Withdrawal); deferred per gd-operations.md lines 141-153.
  • Operation Active -> Completed completion predicate; deferred per gd-operations.md lines 141-153.
  • Operation Active -> Failed failure predicate; deferred per gd-operations.md lines 141-153.
  • Working-name promotion for on_action OperationActivated, OperationPhaseAdvanced, OperationCompleted, OperationCancelled, OperationFailed labels; deferred to Wave 5a.

MC Condition Ladders:

  • Per-condition apply / tend / clear activity bindings; deferred per ad-0012 lines 187-194.
  • Per-condition rung-step Config field names (the exact threshold shape for each ladder rung); deferred per ad-0012 lines 102-110 (a balance pass owned by Wave 5a runtime-backing).
  • Per-condition terminal-rung acquired-trait conversion duration (Config.Condition.<Name>.TerminalConversionDuration); deferred per ad-0012 lines 95-104.
  • Working-name promotion for on_action ConditionApplied, ConditionImproved, ConditionConverted labels; deferred per ad-0012.

Per-Act gate predicates:

  • Acts 1 -> 2 through 6 -> 7 predicates are authored in the seven cited gd-act-N-gate-spec.md docs. Each gate spec owns Config field names for its predicate thresholds; Wave 5a runtime-backing closes the Config wiring and the named-state runtime evaluation.

Feature / Territory / Site State Ladders:

  • Transition row tables for FeatureDiscoveryState (9 transitions), TerritoryKnowledgeState (per gd-state-axes-and-thresholds.md lines 110-127), and TerritoryContentState (per gd-state-axes-and-thresholds.md lines 153-164) are deferred to a follow-up read-model-side wave that authors them against this hub's 12-column schema.

Runtime Backing Status

Status: contract-only. No state-machine runtime exists today for any of the five new families. The existing Front / Force / Operation / DefenseZone surfaces have committed scopes and read-model files, but the read-model files project nothing today (per gd-forces-phase-b-design-questions.md line 492); Wave 5a populates them.

  • Host/runtime owners (proposed; none exist today): legacy/v1/examples/valenar/Host/Systems/QuestThreadSystem.cs, MissionSystem.cs, ActProgressionSystem.cs, RealmRankSystem.cs, ProvinceDefensePlanSystem.cs. The ThreatSource / FrontCandidate aggregation flows through the 6-phase channel pipeline rather than a per-tick aggregator system (per ad-0008 lines 74-77, 170-180).
  • Generated / .secs owners (proposed; none exist today): Content/quests/scopes.secs and Content/quests/templates/*.secs for QuestThread / Mission; Content/acts/scopes.secs and Content/acts/systems/act_progression.secs for ActState; Content/realm/scopes.secs and Content/realm/systems/rank_transition.secs for Realm; extensions on Content/territories/channels.secs, Content/provinces/channels.secs, Content/threat/scopes.secs, and Content/fronts/scopes.secs for emergence. Matching Generated/ stand-ins are authored in the same Wave 5a runtime-backing wave.
  • Read-model / UI owners: existing Front / Force / Operation / DefenseZone read-model files exist but project nothing today; proposed new files Host/ReadModels/QuestThreadsReadModel.cs, MissionsReadModel.cs, ActStateReadModel.cs, RealmReadModel.cs, ThreatSourcesReadModel.cs, FrontCandidatesReadModel.cs. UI surfaces consuming these read-models include the Crown-scale strategic map, the Current Plan rail, the Journal, the Objectives screen, the Territory Dossier, the Core Screen saga-arc display, the Character Screen condition strip, and the Settlement Screen tier indicator.
  • Tests: deferred to Wave 7 (post-Wave 5a runtime-backing). Each transition row carries a <deferred-to-wave-7> test name in the tests_required column; the names are greppable so Wave 7 authors can produce a flat list of every test row this hub generates.
  • Known gaps: every <deferred-to-Wave-5a-runtime-backing> cell in the tables above. The aggregated registry of those cells is the ## Open Follow-On Items section above; Wave 5a authors consume that registry to plan the runtime-backing work.
  • Illegal fallback behavior: NO silent default transition. NO day-count predicates. NO real-time-clock predicates. NO magic threshold constants — use named Config.<Subsystem>.<FieldName> Config fields with world-size parameterized defaults (per Discipline 14 of .claude/rules/orchestrator-mode.md). NO Modifier bound as a state carrier for any of the five new state-machine families (Modifiers are the carrier ONLY for MC conditions and injuries per ad-0012 lines 111-126 and ad-0013 lines 111-124). NO silent Realm.PolityRank = Outpost default; the channel must read through the 6-phase pipeline. NO silent advance of ActState.CurrentAct without a gate predicate evaluating true. NO silent inheritance of DefenseZone Posture across Province boundaries (per gd-defense-zones.md ## PlanState).
  • Next closure wave: Wave 5a runtime-backing wave authors the host systems, the Content/ source extensions, the matching Generated/ stand-ins, the on_action working-name promotions, and the Config-field wiring for every transition row deferred above. Parallel work in the same Wave 5a closes the ad-0008 emergence channels and the dynamic-quest-pressure-model storyBias formula.