ADR 0008 — Pressure-to-Front Emergence as Persistent Scope State Machine
Context
Valenar's Front model in
../../systems/gd-fronts.md commits the
Front as a Crown-scale strategic noun grouping ProvinceDefensePlans,
Forces, Territories, Routes, Sites, and Threats. The Front itself is
already a typed entity with a FrontStatus enum and authored campaign
goals. What is not committed is the layer beneath the Front: how
world-state pressure (Corruption escalation, ward failure, gate
activity, route safety breakdown, hostile faction claims) becomes a
named, queryable, player-actionable emergence that can be elevated into
a Front. Today every act doc, the dynamic quest pressure contract, and
the realm-ranks ladder treat "pressure rising into a Front" as if a
single per-tick event or a per-tick aggregator could carry the
transition. That is the gap this ADR closes.
Two patterns were viable for the emergence layer. The first was a
per-tick event family: a PressureSignal aggregator system runs each
tick, summing inputs (Corruption, ward state, gate activity, route
safety), comparing the sum against a threshold constant, and firing
PressureRoseToFront when the threshold is crossed. The second was
binding the emergence layer to a Modifier — declaring a
Modifier(PressureCluster) shape that attaches to a province when
pressure rises, with the Modifier carrying the cluster's identity. Both
patterns collapse under the AAA precedent. CK3 Casus Belli is a
persistent queryable claim that the player elevates by deliberate
decision, not a per-tick event. 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 one
structure: the emergence layer is persistent named state with its own
lifecycle, not a transient event and not a Modifier. Modifiers are
reusable effect bundles, not named persistent entities; per-tick events
encode the transition, not the carrier.
The Foundation Hardening needs the emergence-to-Front pathway committed before the urgency-signal binding wave can read Province-level pressure, before the dynamic quest pressure model can generate pressure missions against named emergence entities, and before the Wave 3a state-machine row for the pressure-corruption cross-act lane can land.
Decision
The pressure-to-front pathway is committed as a six-tier persistent SECS-scope state machine, with events firing AT transitions rather than as the emergence carrier itself.
Tier 1 — Territory-level inputs. The existing
TerritoryData.Corruption channel remains the primary input. Three
additional per-Territory channels are declared in
legacy/v1/examples/valenar/Content/territories/channels.secs (and
matched in the parallel Generated/Territories/): gate-activity,
ward-failure, and route-safety. The exact channel kind (Base /
Accumulative / Contributed) and exact field names are deferred to the
Wave 5a runtime-backing wave; this ADR commits that these three
Territory-level channels exist as a primitive class, not their
finalized signatures.
Tier 2 — Province-level aggregation through the 6-phase channel
pipeline. Three new contributory channels are declared on the
Province scope: ProvinceThreatPressure, ProvinceWardIntegrity, and
ProvinceRouteSafety. Aggregation flows through the committed 6-phase
channel resolution pipeline (base -> additive -> multiplicative ->
HardOverride -> clamp -> return) — not through an aggregator
system that compares the sum against magic-constant thresholds. The
"Never tune parameters as a substitute for fixing architecture" tenet
applies here directly: thresholds are clamp / source data, not the
architectural carrier.
Tier 3 — scope ThreatSource. A new scope rooted at Province that
walks to the underlying Territory, Site, or Route causing the threat.
Fields committed in this ADR: Kind: int (typed enum to be authored
in Wave 5a; values include taint cluster, gate scarring, hostile
faction encroachment, ward-line breach), Severity: channel (read via
the 6-phase pipeline), DiscoveryState: int (Hidden / Suspected /
Confirmed / Reported), AnchorTerritoryId, AnchorSiteId, and
AnchorRouteId. ThreatSource is the first scope row in the emergence
ladder: it represents an identified, queryable cause-of-pressure that
the player or the dynamic quest pressure generator can act on.
Tier 4 — scope FrontCandidate. A new scope rooted at Crown (or
at the Realm scope committed in
./ad-0009-realm-scope-and-polity-rank-orthogonality.md)
that aggregates one-or-more ThreatSources into a candidate Front. Fields
committed in this ADR: ProposedHqTerritoryId, CoveredProvinceIds: ScopedList<FrontCandidateProvinceRef>, AggregatedSeverity: channel,
ProposedCampaignGoals: ScopedList<FrontCandidateObjective>, and
DismissedByCrown: int. The FrontCandidate is the layer the Crown
player sees on the Crown-scale strategic map BEFORE the Front itself
exists: a recommended Front the player can elevate, dismiss, or
ignore.
Tier 5 — Front declaration by Crown decision. The Crown player
elevates a FrontCandidate to a Front by explicit decision (a queued
Crown-scale Activity that calls
SetStatus(Forming) on the existing Front scope and binds the
elevated FrontCandidate's ThreatSources, ProposedHqTerritoryId, and
ProposedCampaignGoals into the new Front). Autonomous Crown automation
that elevates FrontCandidates without player decision is explicitly
deferred — the player remains the Crown until a later wave commits a
Crown-AI policy.
Tier 6 — Events at transitions. Three on_action labels fire at
the three state transitions: ThreatSourceMaterialized(threatSourceId)
when a new ThreatSource scope row is created, FrontCandidateRaised( candidateId) when one or more ThreatSources aggregate into a new
FrontCandidate, and FrontDeclared(frontId, candidateId) when the
Crown player elevates a FrontCandidate to a Front.Forming row. These
on_action labels are metadata for the existing SECS event-subscription
machinery (per .claude/rules/behavior-vocabulary.md and the on_action
allowlist in secs-concepts.md); they are not a new behavior keyword.
Alternatives Considered
Per-tick event family with a PressureSignal aggregator system.
Rejected because the aggregator must hold a magic threshold constant
to decide when to fire — and "Never tune parameters as a substitute
for fixing architecture" forbids that pattern. The aggregator also
produces no queryable identity: there is no ThreatSource the player
can read, no FrontCandidate the Crown-scale map can show, and no place
for the dynamic quest pressure generator to attach a pressure mission.
The AAA precedent set is universal in rejecting transient-event
emergence: CK3 / EU5 / Stellaris / RimWorld / Total War all carry
persistent named state for the emergence layer, not transient events.
Modifier-bound emergence (e.g., Modifier(PressureCluster) attached
to a province when pressure rises). Rejected because Modifiers are
reusable effect bundles per secs-concepts.md, not named persistent
entities. A Modifier has no Kind, no AnchorTerritoryId, no
ProposedCampaignGoals field — those would force the Modifier
mechanism into a parameter-tuning surface, the same anti-pattern the
tenets call out. Modifiers may still attach to a ThreatSource or
FrontCandidate to express ward-failure decay or pressure spread, but
they are not the carrier of emergence identity.
Tenets Applied
- "Never tune parameters as a substitute for fixing architecture." Magic-threshold aggregators are explicitly rejected; aggregation flows through the committed 6-phase channel pipeline so thresholds live as clamp values and source data rather than as architectural-decision constants.
- "Research before designing." The AAA precedent for the emergence layer (CK3 Casus Belli; CK3 Struggle / Mongol Invasion; EU4 / EU5 Disasters and Situations; Stellaris Galactic Crises Prethoryn / Contingency / Unbidden; RimWorld threat curve; Total War campaign-map agent emergence) was consulted before committing the six-tier shape.
- "Future-proofed solutions only." Six tiers (Territory inputs, Province aggregation, ThreatSource, FrontCandidate, Front declaration, transition events) leave room for later autonomous Crown-AI policies, additional ThreatSource kinds, and additional derived Province-level pressure channels without reshape.
- "No backwards compatibility." Neither the per-tick aggregator pattern nor the Modifier-bound emergence pattern is preserved.
Runtime Backing Status
Status at this ADR's acceptance time: contract-only.
- Host/runtime owner:
legacy/v1/examples/valenar/Host/Data/ThreatSourceData.cs,legacy/v1/examples/valenar/Host/Data/FrontCandidateData.cs, and the existinglegacy/v1/examples/valenar/Host/Data/FrontData.csremain the host-data anchors; the new files are authored in the Wave 5a runtime-backing wave. The existinglegacy/v1/examples/valenar/Host/Systems/tree gains a PressureAggregation host-side system only if the 6-phase channel pipeline alone cannot carry the aggregation; default intent is to carry aggregation through declared channels and no per-tick host system. - Generated/.secs owner:
legacy/v1/examples/valenar/Content/territories/channels.secs(new Territory-level input channels),legacy/v1/examples/valenar/Content/provinces/channels.secs(newProvinceThreatPressure,ProvinceWardIntegrity,ProvinceRouteSafetycontributory channels),legacy/v1/examples/valenar/Content/threat/scopes.secs(newscope ThreatSource),legacy/v1/examples/valenar/Content/fronts/scopes.secs(newscope FrontCandidatealongside the existingscope Front). Matching Generated stand-in trees authored in the same Wave 5a runtime-backing wave. - Read-model/UI owner: the Crown-scale strategic map view; the Valenar React client gains a FrontCandidate roster surface alongside the existing Front roster. Authored in the runtime-backing wave's UI follow-up.
- Tests: deferred to the Wave 5a runtime-backing wave.
- Known gaps: the exact
ThreatSource.Kindenum value list; the Severity channel's exact 6-phase composition; the Crown-scale Activity that performsFront.SetStatus(Forming)from a FrontCandidate; the FrontCandidate-to-Front binding rule for ThreatSources, ProposedHqTerritoryId, and ProposedCampaignGoals. - Illegal fallback behavior: 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 ThreatSourceorscope FrontCandidate. NoFront.Statusvalue may be set toFormingoutside the Crown player's explicit Front declaration Activity until a later wave commits autonomous Crown-AI emergence. - Next closure wave: Wave 5a runtime-backing wave authors the
channels, the scope rows, the host data classes, the Crown-scale
declaration Activity, and the matching Generated stand-ins. The
urgency-signal binding wave is the earliest consumer of
ProvinceThreatPressure.
Glossary Propagation
The following terms MUST land in gd-glossary.md, gd-canon.md, and
README.md:
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).ThreatSourceMaterialized,FrontCandidateRaised,FrontDeclared(on_action transition labels).
Consequences
For
../../systems/gd-fronts.md, the
existing FrontStatus.Forming transition is now bound to the Crown
player's explicit Front declaration Activity against a FrontCandidate.
The Front doc may be extended in a later wave to reference
FrontCandidate as the prior-to-Forming state; this ADR does not edit
that doc.
For [../../systems/gd-corruption-reclamation.md] and the
pressure-corruption cross-act lane in
./ad-0006-dynamic-quest-pressure-and-cross-act-lane-decisions.md,
the lane gains a typed entity (ThreatSource) to bind missions and
urgency signals against. The dynamic quest pressure generator may
emit pressure missions tied to a specific ThreatSource id or to a
specific FrontCandidate id.
For
../../systems/gd-realm-ranks-and-polity.md
and the Crown rank, the Crown player's authority over the Front roster
is extended to authority over the FrontCandidate roster: the Crown
chooses which candidates to elevate, dismiss, or defer.
For future verifier checks, any doc that introduces a per-tick aggregator system with a hardcoded pressure threshold as the carrier of emergence is a tenet FAIL. Any doc that introduces an emergence shape outside the six committed tiers above must supersede this ADR.
References
./ad-0006-dynamic-quest-pressure-and-cross-act-lane-decisions.md./ad-0013-scope-state-channel-template-archetype-pattern.md../../systems/gd-fronts.md../../systems/gd-defense-zones.md../../systems/gd-realm-ranks-and-polity.md../../systems/gd-dynamic-quest-pressure-model.md.claude/rules/secs-concepts.md(the 6-phase channel pipeline, the on_action vocabulary).claude/rules/behavior-vocabulary.md(the on_action allowlist).claude/rules/valenar-contract-backing.md