refactor(policy): extract combat selection and context modules

This commit is contained in:
0xrsydn 2026-09-22 12:45:46 +07:00
commit 484551047c
9 changed files with 1247 additions and 1167 deletions

66
docs/POLICY.md Normal file
View file

@ -0,0 +1,66 @@
# 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.