Skip to main content

Front Emergence: Pressure to ThreatSource to FrontCandidate to Front

Context

This page owns the six-tier emergence ladder from world-state pressure through ThreatSource and FrontCandidate up to the existing Front primitive. The Front itself is owned by gd-fronts.md; this page commits the layer beneath the Front: how Corruption escalation, ward failure, gate activity, route safety breakdown, and similar world-state pressure becomes a named, queryable, player-actionable emergence that the Crown player can elevate into a Front.

The binding authority is ../lore/adr/ad-0008-pressure-to-front-emergence-scope-state-machine.md; this page is the owner-doc surface that ad-0008 routes the emergence ladder to.

The cross-cutting canonical-pattern ADR ../lore/adr/ad-0013-scope-state-channel-template-archetype-pattern.md records that ThreatSource and FrontCandidate are pattern instantiations: scope X plus state channel plus implicit archetype (per ad-0013 lines 90-99: ThreatSource's archetype is the per-Kind authored data; FrontCandidate's archetype is implicit since FrontCandidates are generated from ThreatSource clusters rather than from an authored catalog).

Emergence is persistent named state, not transient per-tick events and not Modifier-bound state. The "Research before designing" tenet was applied: CK3 Casus Belli, CK3 Situations (Struggle, Mongol Invasion), EU4/EU5 Disasters and Situations, Stellaris Galactic Crises (Prethoryn, Contingency, Unbidden), Imperator triggers, RimWorld threat curve, and Total War campaign-map agent emergence all share the same shape: emergence is persistent state with its own lifecycle, with events firing at transitions rather than as the emergence carrier.

Six-Tier Emergence Ladder

The pathway runs in six tiers, top-to-bottom:

  1. Territory-level inputs. Per-Territory channels carrying the raw pressure signal.
  2. Province-level aggregation. Per-Province contributory channels that aggregate Territory inputs through the 6-phase channel pipeline.
  3. scope ThreatSource. Province-rooted scope identifying a queryable cause-of-pressure.
  4. scope FrontCandidate. Crown- or Realm-rooted scope clustering one or more ThreatSources into a candidate Front.
  5. Front declaration by Crown activity. The Crown player elevates a FrontCandidate to a Front scope by an explicit Crown-scale activity.
  6. Events at transitions. on_action labels fire at the three transitions above.

Each tier is committed below. Aggregation between tiers flows through the committed 6-phase channel resolution pipeline (baseadditivemultiplicativeHardOverrideclampreturn) per ../../../../../.claude/rules/secs-concepts.md. A per-tick aggregator system with a hardcoded threshold is explicitly forbidden (ad-0008 lines 74-77 and 203-209) — that pattern is a tenet FAIL.

Tier 1: Territory-Level Inputs

The existing TerritoryData.Corruption channel remains the primary input. Three additional per-Territory channels are committed by ad-0008 lines 60-66 as a primitive class:

  • gate-activity. Per-Territory pressure signal for gate scarring, gate openings, gate-network reactivation. Field name <deferred-to-Wave-5a-runtime-backing> per ad-0008 lines 62-66.
  • ward-failure. Per-Territory pressure signal for ward-line breaches, failing Ancient Shield segments, ward stone decay. Field name <deferred-to-Wave-5a-runtime-backing> per ad-0008 lines 62-66.
  • route-safety. Per-Territory pressure signal for route breakdown, ambush risk, hostile-faction interdiction. Field name <deferred-to-Wave-5a-runtime-backing> per ad-0008 lines 62-66.

The exact channel kind (Base, Accumulative, or Contributed) for each of the three new Territory channels is <deferred-to-Wave-5a-runtime-backing> per ad-0008 lines 62-66. This page commits that the channels exist and that they live in legacy/v1/examples/valenar/Content/territories/channels.secs (with the matching Generated stand-in under legacy/v1/examples/valenar/Generated/Territories/).

Tier 2: Province-Level Aggregation

Three new contributory channels are committed on the Province scope by ad-0008 lines 70-77:

  • ProvinceThreatPressure. Aggregates Territory-level taint / corruption / hostile-faction pressure across the Province.
  • ProvinceWardIntegrity. Aggregates Territory-level ward-failure across the Province.
  • ProvinceRouteSafety. Aggregates Territory-level route-safety across the Province.

Aggregation flows through the committed 6-phase channel resolution pipeline. Each Province channel is a Contributed channel that reads Territory contributions, applies additive and multiplicative phases, and clamps in the clamp phase. Thresholds live as clamp values and source data, not as architectural-decision constants.

Forbidden: a per-tick host-side PressureAggregation system that sums Territory inputs and compares the sum against a magic threshold constant. ad-0008 lines 74-77 and 203-209 are explicit: that pattern is a tenet FAIL. The 6-phase channel pipeline carries aggregation; a host-side aggregator system is authored only if the channel pipeline alone cannot carry the case, and only as a contract-only deferred follow-up.

The exact 6-phase composition (which Territory inputs feed which Province phase) for each of the three Province channels is <deferred-to-Wave-5a-runtime-backing> per ad-0008 lines 197-202.

Tier 3: scope ThreatSource

scope ThreatSource is rooted at Province and walks to the underlying Territory, Site, or Route causing the threat. Fields committed by ad-0008 lines 79-89:

  • Kind: int. Typed enum. Value list is <deferred-to-Wave-5a-runtime-backing> per ad-0008 lines 197-202. The value-list intent committed in ad-0008 is taint cluster, gate scarring, hostile faction encroachment, ward-line breach; the exact enum is authored in Wave 5a.
  • Severity: channel. Read via the 6-phase pipeline. Channel composition <deferred-to-Wave-5a-runtime-backing> per ad-0008 lines 197-202.
  • DiscoveryState: int. Four-rung ladder: Hidden, Suspected, Confirmed, Reported. Order is one-directional in the common case (lower-rung-to-higher-rung as the player surveys), with explicit authoring permitted for retreat (a Confirmed ThreatSource that goes unobserved long enough may retreat to Suspected via authored decay; decay rule <deferred-to-Wave-5a-runtime-backing>).
  • AnchorTerritoryId. Optional anchor when the cause is a Territory.
  • AnchorSiteId. Optional anchor when the cause is a Site.
  • AnchorRouteId. Optional anchor when the cause is a Route.

ThreatSource is the first scope row in the emergence ladder. It is the queryable cause-of-pressure that the player and the dynamic quest pressure generator can bind missions to.

ThreatSource retirement. ad-0008 commits ThreatSourceMaterialized as the creation transition but does not commit an explicit retirement lifecycle enum. Retirement state is therefore <deferred-to-Wave-5a-runtime-backing> per ad-0008 lines 87-89 and the intent committed in the binding ADR. The Wave 5a runtime-backing wave authors either an explicit retirement state on DiscoveryState-like shape, or a separate ThreatSourceLifecycleState enum, or a typed retirement flag — that choice is the Wave 5a author's, bound by the ad-0013 canonical pattern.

Tier 4: scope FrontCandidate

scope FrontCandidate is rooted at Crown (or at the Realm scope per ../lore/adr/ad-0009-realm-scope-and-polity-rank-orthogonality.md) and aggregates one or more ThreatSources into a candidate Front. Fields committed by ad-0008 lines 91-100:

  • ProposedHqTerritoryId. The proposed HQ Territory if the candidate is elevated to a Front.
  • CoveredProvinceIds: ScopedList<FrontCandidateProvinceRef>. The Provinces the candidate proposes the Front cover.
  • AggregatedSeverity: channel. Read via the 6-phase pipeline; composes the Severity of its bound ThreatSources.
  • ProposedCampaignGoals: ScopedList<FrontCandidateObjective>. The proposed campaign goals if the candidate is elevated.
  • DismissedByCrown: int. Flag for Crown dismissal (see Tier 5 below).

FrontCandidate is the layer the Crown player sees on the Crown-scale strategic map before the Front itself exists. It is a recommended Front the player can elevate, dismiss, or ignore.

FrontCandidate explicit lifecycle states. ad-0008 does not commit a named FrontCandidateLifecycleState enum; the lifecycle is implicit in the field set above. The intended states — Raised, Active, Dismissed, Elevated — are <deferred-to-Wave-5a-runtime-backing> per ad-0008 lines 91-110 and the intent committed in the binding ADR. The Wave 5a runtime-backing wave decides whether to add an explicit lifecycle enum or to keep the lifecycle implicit in DismissedByCrown plus the existence of a downstream Front row.

Tier 5: Front Declaration by Crown Activity

The Crown player elevates a FrontCandidate to a Front by explicit Crown-scale activity. The activity, committed by ad-0008 lines 102-110:

  • Calls Front.SetStatus(Forming) on a newly created or selected scope Front row.
  • Consumes the FrontCandidate (sets it to the <deferred-to-Wave-5a-runtime-backing> Elevated state per Tier 4).
  • Binds the FrontCandidate's ThreatSources into the new Front's ThreatSource list.
  • Binds the FrontCandidate's ProposedHqTerritoryId into the new Front's HQ Territory.
  • Binds the FrontCandidate's ProposedCampaignGoals into the new Front's ScopedList<FrontObjective> rows.

Crown autonomy: per ad-0008 lines 107-110, autonomous Crown automation that elevates FrontCandidates without the player's Crown-scale activity is explicitly deferred. The player remains the Crown until a later wave commits a Crown-AI policy. Dismissal works the same way: the player explicitly sets DismissedByCrown on the FrontCandidate. Auto-dismissal of stale candidates is <deferred-to-Wave-5a-runtime-backing>.

The existing FrontStatus enum in gd-fronts.md lines 178-190 covers the rest of the Front lifecycle from Forming onward; this page does not redefine it.

Tier 6: Events at Transitions

Three on_action labels fire at the three state transitions, committed by ad-0008 lines 112-120:

  • ThreatSourceMaterialized(threatSourceId) fires when a new scope ThreatSource row is created.
  • FrontCandidateRaised(candidateId) fires when one or more ThreatSources aggregate into a new scope FrontCandidate row.
  • FrontDeclared(frontId, candidateId) fires when the Crown player elevates a FrontCandidate to a Front.Forming row.

All three label names are working names; promotion to committed labels is deferred to Wave 5a per the same pattern as the QuestThread / Mission labels in ./gd-quest-thread-and-mission-state-machine.md. The on_action keyword is the committed metadata vocabulary per ../../../../../.claude/rules/behavior-vocabulary.md; the label names themselves are committed when Wave 5a authors them in Content/fronts/events.secs and Content/threat/events.secs.

Events fire at transitions; they are not the carrier of emergence. The carrier is the persistent scope row. ad-0008 lines 124-133 records the rejection of per-tick event emergence as the carrier.

Runtime Backing Status

Status: contract-only.

  • Host/runtime owner: proposed legacy/v1/examples/valenar/Host/Data/ThreatSourceData.cs and legacy/v1/examples/valenar/Host/Data/FrontCandidateData.cs. The existing legacy/v1/examples/valenar/Host/Data/FrontData.cs remains the host-data anchor for scope Front. No new per-tick host system is required by default: aggregation flows through the 6-phase channel pipeline. A PressureAggregation host system is authored only as a contract-only follow-up if the channel pipeline alone cannot carry the aggregation, never as a magic-threshold per-tick system. Per ad-0008 lines 170-180.
  • Generated/.secs owner: legacy/v1/examples/valenar/Content/territories/channels.secs (new Territory-level input channels: gate-activity, ward-failure, route-safety), legacy/v1/examples/valenar/Content/provinces/channels.secs (new ProvinceThreatPressure, ProvinceWardIntegrity, ProvinceRouteSafety contributory channels), legacy/v1/examples/valenar/Content/threat/scopes.secs (new scope ThreatSource), legacy/v1/examples/valenar/Content/fronts/scopes.secs (new scope FrontCandidate alongside the existing scope Front), legacy/v1/examples/valenar/Content/threat/events.secs and legacy/v1/examples/valenar/Content/fronts/events.secs (the three working-name on_action labels promoted to committed labels). Matching Generated stand-in trees authored in the same Wave 5a runtime-backing wave.
  • Read-model/UI owner: proposed legacy/v1/examples/valenar/Host/ReadModels/ThreatSourcesReadModel.cs and legacy/v1/examples/valenar/Host/ReadModels/FrontCandidatesReadModel.cs. Neither exists today. UI consumers are the Crown-scale strategic map view in the Valenar React client (which gains a FrontCandidate roster alongside the existing Front roster), and the territory dossier in ../ux/gd-territory-dossier.md.
  • Tests: <deferred-to-wave-7> — Wave 7 test-authoring wave authors unit tests against each transition. Test names use the <StateMachine><Transition>Test convention (e.g. ThreatSourceMaterializationTest, FrontCandidateRaisedTest, FrontDeclaredFromCandidateTest).
  • Known gaps: the exact ThreatSource.Kind enum value list; the Severity and AggregatedSeverity channels' exact 6-phase composition; the exact field name and channel kind of the three new Territory inputs (gate-activity, ward-failure, route-safety); the Crown-scale Activity that performs Front.SetStatus(Forming) from a FrontCandidate; the FrontCandidate-to-Front binding rule for ThreatSources, ProposedHqTerritoryId, and ProposedCampaignGoals; ThreatSource retirement state and FrontCandidate explicit lifecycle states.
  • Illegal fallback behavior: per ad-0008 lines 203-209, no per-tick aggregator system with a hardcoded threshold may stand in for the 6-phase channel pipeline. No Modifier-as-emergence-carrier may stand in for scope ThreatSource or scope FrontCandidate. No Front.Status value may be set to Forming outside the Crown player's explicit Front declaration activity until a later wave commits autonomous Crown-AI emergence. An attempt to bypass the Crown-elevation activity and set Front.Status directly raises an explicit InvalidStateException.
  • Next closure wave: Wave 5a runtime-backing wave authors the channels, the scope rows, the host data classes, the Crown-scale declaration activity, the matching Generated stand-ins, and the two read-model surfaces. The urgency-signal binding wave is the earliest downstream consumer of ProvinceThreatPressure.

Glossary Propagation

The following terms must land in the canonical glossary surfaces (../gd-glossary.md, ../gd-canon.md, ../README.md) per ../../../../../.claude/rules/valenar-contract-backing.md § Canonical Glossary Targets. Glossary propagation itself is the next docs wave (Wave 3c); this section records the obligation:

  • ThreatSource (scope primitive).
  • FrontCandidate (scope primitive).
  • PressureCluster (informal design term used to describe a ThreatSource cluster that has aggregated into a FrontCandidate; not a committed runtime entity by itself).
  • ProvinceThreatPressure, ProvinceWardIntegrity, ProvinceRouteSafety (Province-level contributory channel names).
  • ThreatSource.DiscoveryState ladder (Hidden, Suspected, Confirmed, Reported).
  • ThreatSource.Kind (enum; value list deferred to Wave 5a).
  • AnchorTerritoryId, AnchorSiteId, AnchorRouteId (anchor fields on scope ThreatSource).
  • CoveredProvinceIds, ProposedHqTerritoryId, AggregatedSeverity, ProposedCampaignGoals, DismissedByCrown (fields on scope FrontCandidate).
  • ThreatSourceMaterialized, FrontCandidateRaised, FrontDeclared (on_action transition labels; working names).

Cross-References