A House Divided A House DividedDocumentation
Changelog
Game Design/World & Simulation

Bloc Alignment and Spheres of Influence

Last updated 2026-08-20
Source files

Overview#

Alignment tracks how drawn each nation is toward each Cold War pole (0-100 shares that sum with an "uncommitted" remainder). Spheres of influence are the downstream effect: which patron a nation actually belongs to right now. Alignment is the cause, sphere membership is the effect, sphereProjection.ts is the seam between the two systems.

Location: src/lib/alignment/

Key files:

Bloc Stress#

A bloc's own cohesion is modeled, not just its members' individual shares, an alliance that grabs half the map should not defend all of it as easily as a compact one. Stress is a 0-1 gauge (computeBlocStress()) built from three weighted components of the bloc's own membership:

Component Weight Meaning
Contested 0.45 Fraction of members where a rival pole holds >= CONTESTED_RIVAL_MIN (25) share
Wants out 0.35 Fraction of members with wantsOutSinceTurn set (sustained-leave signal from membershipEligibility.ts)
Digesting 0.20 Fraction of members that joined within DIGESTION_WINDOW_TURNS (12 turns)

Raw member count is deliberately not an input, a large bloc of settled, uncontested members should not be penalized for size alone.

blocPlayEffectiveness(stress) returns a multiplier on the bloc's own plays: 1 - STRESS_MAX_DAMPING * stress, where STRESS_MAX_DAMPING = 0.4. A fully-stressed bloc is impaired (60% effectiveness), never inert. blocStressLabel() buckets the gauge into Settled (<0.25), Strained (<0.6), or Overextended (>=0.6) for player-facing display.

Sphere Projection#

projectSphereAlignment() converts a nation's pole shares (0-100) into the sphere system's per-sponsor alignment scale (0-1), one entry per sponsored pole in the current era (including zero-share sponsors, so a lapsed patron shows as lapsed rather than disappearing).

primarySphereFor() determines which patron a nation is actually committed to: it takes the nation's top pole and requires lead >= joinGateForPoleCount(poleCount) (the same gate the alignment Ledger's bands use) before returning a sponsor. Below that gate, nobody has committed the nation and there is no primary sphere.

applyAlignmentToMembership() writes projected alignment through onto an existing SphereMembership document:

Era Crossing#

applyEraCrossing() re-projects a nation's pole shares onto the poles of a new alignment era, run exactly once per crossing (guarded by a stored era key so a long game converts cleanly instead of drifting between vocabularies or re-converting every turn).

Drift (Passive Movement)#

Drift is bounded background movement, small enough that a deliberate influence play always outweighs it (computeDrift(), ).

Membership Pull#

Belonging to an alignment-channel organization exerts its own small passive pull toward that org's pole (membershipPullForTurn()):

Membership Eligibility#

standingFor() (membershipEligibility.ts) answers whether a nation can join, or should leave, an alignment-governed organization, measured on the nation's share in that org's pole, not its overall lead.