Skip to main content

ADR 0009 — Realm Scope and PolityRank Orthogonal to Settlement.Stage

Context

Valenar's polity ladder is committed at ../../systems/gd-realm-ranks-and-polity.md: Settlement -> Banner Network -> Province -> Crown, with later reclamation projects operating at realm scale. That doc also commits the rule that there is no single Realm-birth moment — polity emerges through a ladder of expanding holdings, routes, obligations, authority, and administration. What is not committed is the runtime shape of the polity ladder: which SECS scope owns the player's polity identity, which channel carries the current ladder rung, and how "settlement size" (the per-Settlement six-tier 0..VII population stage already authored in gd-settlements-and-outposts.md) relates to "polity rank" (the player's Realm-scale authority).

The drift problem is real. Three current docs use the word "Stage" for two different things: the per-Settlement size stage (population gates from Outpost to Capital) and the Act-progression stage (the seven Act-Arc tiers from Act 0 to Act 7). The ../../acts/README.md tree commits the Act-progression form; [../../systems/gd-settlements-and-outposts.md] commits the population form. Forcing the Realm rank onto either of those Stage fields would collapse three different concerns (population size, polity authority, Act ladder) into one overloaded field. CK3 separates Title tier (Barony / County / Duchy / Kingdom / Empire) from Holding development; EU4 / EU5 separates Country tier from province development; Victoria 3 separates country type from state buildings. Polity rank and settlement development are independent in every shipped reference, and Valenar has the same need.

The Foundation Hardening needs the polity-rank primitive committed before the Wave 3a state-machine row for the faction-polity cross-act lane can land, before DefenseZones (Act 5) and Fronts (Act 6) can declare their unlock gates against a typed scope, and before ./ad-0008-pressure-to-front-emergence-scope-state-machine.md's FrontCandidate scope has a root to walk to.

Decision

A new scope Realm is committed as the singleton root of the player's polity. The Realm scope's lifecycle is one row per game (created at campaign start, retired at campaign end). The Realm scope is the walk target for FrontCandidate, for the existing Front scope, for ProvinceDefensePlan ownership, for Crown-scale Activity authority, and for any later authored scope that needs a Realm-scale root.

A PolityRank enum is committed with the values Outpost, BannerNetwork, Province, and Crown. The current PolityRank value is carried as a Base channel on the Realm scope. The exact channel name is PolityRank and follows the FrontStatus / OperationStatus precedent of state expressed as a typed enum channel on a scope.

The Realm scope's PolityRank is orthogonal to the per-Settlement Stage field. Settlement.Stage continues to carry the per-Settlement population size on the existing 0..VII ladder; that field is per Settlement, not per Realm. PolityRank is one channel on the singleton Realm scope, not a per-Settlement field. Composition: Settlement.Stage gates SettlementRole eligibility (which roles a Settlement of a given size can host); PolityRank gates Realm-scale capability (DefenseZones, Fronts, ProvinceDefensePlans).

PolityRank ladder transitions are driven by Realm-scale triggers, not by any single Settlement crossing a population threshold:

  • Outpost -> BannerNetwork when the Realm covers at least two Settlements connected via Route coverage (the network condition is authored in ../../systems/gd-realm-ranks-and-polity.md as the "Banner Network" rung; the exact Route-coverage predicate is authored in Wave 3a).
  • BannerNetwork -> Province when at least one Settlement under the Realm is designated ProvinceCapital. The Province rank introduces ProvinceDefensePlans and DefenseZones per ../../systems/gd-defense-zones.md.
  • Province -> Crown when at least two Provinces under the Realm have ProvinceCapitals. The Crown rank introduces Fronts per ../../systems/gd-fronts.md.

Capability gates are declared against Realm.PolityRank:

  • DefenseZones unlock at Realm.PolityRank >= Province.
  • Fronts unlock at Realm.PolityRank == Crown.
  • The FrontCandidate scope committed in ./ad-0008 walks to Realm (or, equivalently, to Crown — they refer to the same scope row when PolityRank == Crown).

The Stage-vocabulary overload is acknowledged as a follow-up rename concern but is NOT executed in this ADR. Per the no-fallback redesign-propagation rule and the file-rename stop in .claude/rules/orchestrator-mode.md, a rename of Settlement.Stage or of the Act-progression "stage" wording requires a separate deliberate migration prompt naming exact source and destination paths. This ADR commits the orthogonality decision and flags the rename as a follow-up; it does not perform the rename.

Alternatives Considered

Reuse Settlement.Stage as the polity-rank carrier (promote the first Settlement's Stage to the Realm's PolityRank). Rejected because the cardinality is wrong: many Settlements exist, but the Realm is a singleton. Trying to read PolityRank from "the Realm's first Settlement's Stage" overloads the Settlement Stage field with two unrelated meanings (population size and polity authority), creates a silent dependency on which Settlement was founded first, and produces a Realm rank that drops when that first Settlement is destroyed — none of which matches the design intent. The AAA precedent (CK3 / EU4 / Vic3) explicitly separates polity tier from holding development.

Tag-based polity rank on a player-polity entity (e.g., Tag(Realm.Rank=Crown) applied to a shared entity). Rejected because tags are bind-time predicates, not typed primitives. There is no transition history, no hysteresis support for ladder-down events, and no place to attach Realm-scale per-tick state. The same arguments that rule out tag-set as the Quest Thread carrier in ./ad-0007 apply here.

Tenets Applied

  • "Research before designing." The AAA precedent (CK3 Title tier vs Holding development; EU4 / EU5 Country tier vs province development; Victoria 3 country type vs state buildings) was consulted before committing the orthogonality decision.
  • "Future-proofed solutions only." A singleton Realm scope with a typed PolityRank channel leaves room for later authored Realm-scale channels (e.g., Realm-wide reclamation pressure, Crown-scale supply ratings, Front aggregate severity) without reshape.
  • "No backwards compatibility." Neither the reuse-Settlement-Stage fallback nor the tag-based fallback is preserved. The Realm scope is the polity-rank carrier, full stop.
  • "Templates are data." Realm.PolityRank is per-instance scope state; PolityRank ladder triggers are authored as channel sources / modifiers / systems against the Realm scope, not as hidden host code.

Runtime Backing Status

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

  • Host/runtime owner: a new legacy/v1/examples/valenar/Host/Data/RealmData.cs carries the singleton Realm host data; a new legacy/v1/examples/valenar/Host/Systems/RealmRankSystem.cs reads the ladder predicates and drives PolityRank transitions. Both are authored in the Wave 5a runtime-backing wave.
  • Generated/.secs owner: legacy/v1/examples/valenar/Content/realm/scopes.secs declares scope Realm; legacy/v1/examples/valenar/Content/realm/channels.secs declares PolityRank and any derived Realm-scale channels; matched by the parallel Generated stand-in tree.
  • Read-model/UI owner: the Crown-scale strategic map and the existing Realm-scale UI surfaces in the Valenar React client gain a Realm.PolityRank display; the per-Settlement Stage display remains unchanged.
  • Tests: deferred to the Wave 5a runtime-backing wave.
  • Known gaps: the exact Route-coverage predicate for the Outpost -> BannerNetwork transition; the exact ProvinceCapital designation ceremony / Activity that fires the BannerNetwork -> Province transition; the exact second-Province predicate for Province -> Crown; the on_action labels (working names PolityRankAdvanced(newRank) and PolityRankRegressed(newRank) — promotion to committed metadata is deferred to Wave 5a).
  • Illegal fallback behavior: no Settlement.Stage value may stand in for Realm.PolityRank even temporarily. No tag-set may stand in for the PolityRank enum. No DefenseZone or Front authoring path may unlock without Realm.PolityRank reading the correct value through the 6-phase channel pipeline.
  • Next closure wave: Wave 5a runtime-backing wave authors the host data, the scope, the channels, the host system, the on_action transition labels, and the matching Generated stand-ins.

Glossary Propagation

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

  • Realm (scope primitive — singleton, root of player polity).
  • PolityRank (enum carrying the polity ladder values).
  • Outpost (PolityRank value — distinguished in the glossary from the per-Settlement Outpost-size value where the existing Settlement.Stage ladder names the smallest rung).
  • BannerNetwork (PolityRank value).
  • Province (PolityRank value — distinguished in the glossary from the existing geographic Province scope; both terms remain committed and the glossary must call out the dual meaning).
  • Crown (PolityRank value).

Consequences

For ../../systems/gd-realm-ranks-and-polity.md, the doc gains a follow-up authoring obligation in Wave 3a: extend the existing ladder section with the PolityRank enum, the four ladder transition predicates (Outpost -> BannerNetwork etc.), and the capability-gate section (DefenseZones at Province, Fronts at Crown). This ADR does not edit that doc.

For ../../systems/gd-defense-zones.md and ../../systems/gd-fronts.md, the unlock phrasing "becomes available at Act 5 / Act 6" gains a runtime-backed form: the unlock is gated by Realm.PolityRank >= Province and Realm.PolityRank == Crown respectively. The Act-progression form remains in those docs as the design-pacing description; the runtime-backing form is the PolityRank gate. Both forms must agree.

For the Stage-vocabulary overload: a follow-up rename wave is required to disambiguate Settlement.Stage (population size) from the Act-progression "stage" wording in the ../../acts/README.md tree. This ADR flags the rename as a necessary follow-up but does not perform it. The follow-up rename prompt must name the exact source paths (gd-settlements-and-outposts.md, the ../../acts/gd-act-*.md docs, the Settlement.Stage Generated declarations, the React client UI labels) and the exact destination wording. Until the follow-up rename lands, Settlement.Stage remains the per-Settlement population field and Act ladder docs continue to use "act" rather than "stage" as the canonical ladder noun.

For the verifier, any future doc that conflates Settlement.Stage with Realm.PolityRank, that introduces a polity-rank tag-set, or that introduces a third polity-rank carrier shape is a layering FAIL. Any doc that introduces a fifth PolityRank value beyond the four committed here requires superseding this ADR.

References