How to Work with the State Apparatus AI
Goal-oriented guides for extending, tuning, integrating with, and debugging the state apparatus AI subsystem (Feature 039).
How to Add a New Sub-verb
To add a new sub-verb (e.g., EXTRADITE under REPRESS):
Add the enum member to
StateActionTypeinsrc/babylon/models/enums.py:EXTRADITE = "extradite"
Add it to the parent’s frozenset in
VERB_CHILDREN(src/babylon/models/entities/state_apparatus_ai.py):StateActionType.REPRESS: frozenset({ StateActionType.SURVEIL, StateActionType.INFILTRATE, StateActionType.RAID, StateActionType.PROSECUTE, StateActionType.LIQUIDATE, StateActionType.EXTRADITE, # new }),
Add a budget cost in
_VERB_COSTS(src/babylon/ooda/state_ai/decision.py):StateActionType.EXTRADITE: 12.0,
Add a legitimacy cost in
_LEGITIMACY_COSTS(src/babylon/ooda/state_ai/decision.py):StateActionType.EXTRADITE: 0.08,
Add it to the escalation ladder in
StateApparatusAIDefines(src/babylon/config/defines.py). Insert it at the appropriate position – between PROSECUTE and LIQUIDATE for moderate-severity actions.Write the effect resolution function. Create it in the appropriate module (
repress_effects.pyfor REPRESS sub-verbs):def resolve_extradite( target_org: dict[str, Any], target_key_figure_id: str, destination: str, defines: StateApparatusAIDefines, ) -> tuple[dict[str, Any], float]: """Returns (updated_org, legitimacy_cost).""" ...
Export from
src/babylon/ooda/state_ai/__init__.py:__all__ = [ ... "resolve_extradite", ]
Write tests. At minimum:
Unit test in
tests/unit/state_ai/test_repress_effects.pyverifying the effect math.Verify the enum, VERB_CHILDREN, and
StateActionvalidator accept the new sub-verb.Verify the escalation ladder includes the new sub-verb.
Run the full test suite:
poetry run pytest tests/unit/state_ai/ tests/contract/state_ai/ -v
How to Tune Faction Dynamics
All faction dynamics parameters live in StateApparatusAIDefines
(src/babylon/config/defines.py).
To change how fast factions shift:
Adjust max_faction_shift_per_tick (default: 0.05). Higher values
make the state more reactive to player actions; lower values create
more inertia. Range: [0.0, 0.2].
To change when fascist convergence triggers:
Adjust the three threshold fields:
fascist_security_threshold(default: 0.4) – SS weight neededfascist_settler_ci_threshold(default: 0.6) – settler CI neededfascist_finance_ceiling(default: 0.25) – FC weight ceiling
Lower fascist_security_threshold and fascist_settler_ci_threshold
make convergence easier to trigger. Raise them to make fascism harder
to reach.
To change how hard fascism is to exit:
Adjust the reversion thresholds:
reversion_ss_threshold(default: 0.25)reversion_ci_threshold(default: 0.30)
Lower values make reversion easier; higher values make fascism more persistent.
To verify your tuning:
Run the faction dynamics contract tests:
poetry run pytest tests/contract/state_ai/test_faction_contract.py -v
These tests verify that convergence, reversion, and shift mechanics produce correct behavior under the current configuration.
How to Adjust the REPRESS Pipeline
The REPRESS pipeline has four sub-verbs, each with tunable parameters:
INFILTRATE (intelligence gathering via agents):
infiltrate_informant_intel_rate– intel gained per tick from informants (default: 0.05)infiltrate_provocateur_intel_rate– intel from provocateurs (default: 0.03, lower because provocateurs provoke, not observe)infiltrate_mole_intel_rate– intel from moles (default: 0.08, highest because moles have deepest access)infiltrate_detection_base_chance– base probability of detection per tick (default: 0.1). PROVOCATEUR has 1.5x this chance.
RAID (direct action against organizations):
raid_ci_radicalization_threshold– CI level above which raids radicalize instead of suppress (default: 0.5). This is the consciousness dialectic pivot point.raid_ci_radicalization_boost– CI increase when raids radicalize (default: 0.1)raid_ci_suppression_rate– CI decrease when raids suppress (default: 0.15)raid_org_coherence_damage– coherence damage per raid (default: 0.2)raid_key_figure_capture_base– base capture probability for key figures (default: 0.3). Modified by force level multipliers.
PROSECUTE (legal action against individuals):
prosecute_org_morale_damage– organization morale impact (default: 0.1)prosecute_key_figure_removal_chance– probability of removing targeted key figure (default: 0.6)prosecute_terrorism_charge_multiplier– multiplier for TERRORISM charges (default: 1.5x all effects)
LIQUIDATE (elimination of key figures):
liquidate_core_legitimacy_cost– legitimacy cost in CORE territories (default: 0.15). Requires EMERGENCY_POWERS legislation.liquidate_periphery_legitimacy_cost– legitimacy cost in PERIPHERY territories (default: 0.03)liquidate_deniability_threshold– deniability level that halves legitimacy cost (default: 0.5)
Run the REPRESS contract tests after changes:
poetry run pytest tests/contract/state_ai/test_repress_contract.py -v
How to Use God Mode for Debugging
God mode exposes all state AI internals for development and testing.
Enable god mode by setting god_mode_enabled=True in
StateApparatusAIDefines:
from babylon.config.defines import GameDefines, StateApparatusAIDefines
defines = GameDefines()
state_defines = defines.state_apparatus_ai.model_copy(
update={"god_mode_enabled": True}
)
Read debug state after action selection:
from babylon.ooda.state_ai import RuleBasedStateAI
ai = RuleBasedStateAI()
actions = ai.select_action(
org_id="fbi_001",
faction_balance=balance,
budget=budget,
heat=0.6,
defines=state_defines,
)
debug = ai.get_debug_state(
defines=state_defines,
faction_balance=balance,
budget=budget,
last_actions=actions,
)
# Returns dict with: dominant_faction, stability, legitimacy,
# budget details, action list with costs
When god_mode_enabled=False, get_debug_state() returns None.
How to Integrate with State AI Events
The state AI defines six EventType members for downstream
consumers. To subscribe to state AI events:
Import the event types:
from babylon.models.enums import EventType
Subscribe via the EventBus:
event_bus.subscribe( EventType.STATE_ACTION_EXECUTED, your_handler, ) event_bus.subscribe( EventType.FASCIST_CONVERGENCE, your_handler, )
Available event types:
STATE_ACTION_EXECUTED– any state verb resolvedFASCIST_CONVERGENCE– three-pillar conditions metFACTION_SHIFT– faction weights changedTHREAD_ESCALATION– attention thread advanced phaseLEGAL_FRAMEWORK_ENACTED– new legislation createdLEGAL_FRAMEWORK_REVOKED– legislation revoked
Note
These event types are declared but not yet emitted by any system as of the current implementation. If you need to consume these events, you will need to add emission calls at the appropriate resolution points. See State Apparatus AI (Feature 039) for the dispatch path.
How to Run the Integration Test
The 52-tick integration test validates the full state AI lifecycle: escalation, de-escalation, and convergence.
poetry run pytest tests/integration/test_state_ai_integration.py -v
This test:
Starts with Detroit 2010 defaults (FC=0.45, SS=0.30, SP=0.25)
Simulates increasing player heat over 52 ticks
Verifies verb selection shifts from CO_OPT through REPRESS
Verifies faction balance shifts toward SS under sustained heat
Verifies de-escalation when heat subsides
Validates budget constraints and attention thread allocation
To run all state AI tests (unit + contract + integration):
poetry run pytest tests/unit/state_ai/ tests/contract/state_ai/ \
tests/integration/test_state_ai_integration.py -v
Expected: 499 tests, all passing.
How to Read Player-Visible State Information
The observability module provides functions that convert internal state into player-appropriate representations:
Observable actions (what the player sees when the state acts):
from babylon.ooda.state_ai import create_observable_action
obs = create_observable_action(action, territory_heat=0.7)
# Returns: {verb, sub_verb, target_id, visible_intensity, territory_heat}
Territory observables (what the player sees about territory conditions):
from babylon.ooda.state_ai import create_territory_observables
obs = create_territory_observables(territory_dict)
# Returns: {property_value_proxy, infrastructure_quality, heat,
# population, collective_identity}
Counter-intelligence results (tiered disclosure based on player intel capability):
from babylon.ooda.state_ai import resolve_counter_intel
intel = resolve_counter_intel(
intel_success=0.6,
faction_balance=balance,
last_actions=actions,
defines=defines,
)
# At 0.6: includes faction_balance + action details
Disclosure tiers:
>= 0.0: intel_level + visible actions (verb + target only)>= 0.3: adds faction balance (rounded to 2 decimals)>= 0.6: adds full action details (sub_verb, budget_cost, faction)>= 0.8: adds full state (dominant_faction, stability, legitimacy)
How to Add a New Faction Shift Trigger
To add a new player action that shifts faction balance:
Add the action type key to
_PLAYER_ACTION_SHIFTSinsrc/babylon/ooda/state_ai/faction_dynamics.py:_PLAYER_ACTION_SHIFTS: dict[str, tuple[str, float]] = { "heat_generation": ("security_state", 0.03), "surviving_repression": ("security_state", -0.04), ... "your_new_trigger": ("finance_capital", -0.02), # new }
The tuple is
(faction_to_shift, magnitude). Positive values increase that faction’s weight; negative values decrease it.Call
apply_player_action_shift()from the resolution path where your trigger occurs:from babylon.ooda.state_ai import apply_player_action_shift new_balance = apply_player_action_shift( action_type="your_new_trigger", outcome="success", current_balance=current_balance, defines=defines, )
Outcome
"success"applies full magnitude; any other value applies0.5x.
For material condition triggers (not player-initiated), use
_MATERIAL_CONDITION_SHIFTS and apply_material_condition_shift()
with the same pattern.
See Also
State Apparatus AI (Feature 039) – Complete API reference
About the State Apparatus AI – Design rationale and architecture