# Policy structure ## Current boundaries ```text run.py observe, execute, report results, record the session brain.py entry point, dispatch, navigation, shops, minigames policy/ __init__.py package marker; no registration or initialization context.py Decision, PendingAction, PolicyContext combat.py combat proposals and model questions selection.py card, relic, bundle, hand, and reward proposals facts.py observation parsing and combat calculations ``` `brain.decide()` remains the policy entry point. The runner owns one `PolicyContext` per session and reports action results to it. `brain.Decision` and `brain.PolicyContext` remain available as direct imports of the shared types, so the runner interface does not change. Policy modules may ask Jev for preferences. They must not call the game API. `context.py` does not call either service. Combat arithmetic remains in `facts.py`, not duplicated inside combat policy. The dependency direction is simple: - The runner uses `brain`. - `brain` uses the combat, selection, and context modules. - Combat and selection use context, facts, and the model client. - Context uses only the Python standard library. - No policy module imports `brain` or the runner. ## State and configuration A proposal does not update execution memory. Accepted requests are reconciled with fresh observations. See [policy-state behavior and limits](research/13-policy-state.md) for the evidence required by each guarded action. The existing card-skip setting stays in `brain.CARD_SKIP_POLICY`. The dispatcher passes its value explicitly to `selection.card_reward_decision(skip_policy=...)`. This avoids a circular import and preserves the existing command-line behavior. It is configuration, not per-screen action memory. ## Why stop here These are plain modules, not a plugin framework or class hierarchy. Navigation, shops, and minigames stay together until a further split helps development. The extraction does not change game decisions, confidence gates, fallbacks, state reconciliation, or recording. Run identity and persistent-deck provenance remain separate work. ## Extraction verification - 262 existing assertions passed. No test files or assertions were added for the extraction. - Thirteen isolated whole-process scenarios passed with local HTTP fixtures. - All 346 stored observations produced identical decisions before and after extraction, in both model-free mode and local-model-stub mode: 692 comparisons. - The local model requests also matched, including their state and questions. - Thirty-six function/class definitions retained identical abstract syntax trees. Only the two dispatch functions and explicit card-skip parameter changed. - Dataset integrity, Python compilation, and shell syntax checks passed. The differential and whole-process probes were temporary. They made no live game or paid model calls. These checks establish refactoring equivalence for the tested inputs, not correctness for every possible game state.