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.

View file

@ -6,9 +6,9 @@ This pass replaces per-screen globals with explicit session memory. It follows
[the correctness pass](12-correctness-pass.md). It does not redesign recording,
identify runs, or establish persistent-deck provenance.
`brain.py` remains one file. Moving policy code and changing its state behavior
at the same time would make failures harder to diagnose. A later extraction
can preserve the interface and the runner-level tests.
This state pass kept `brain.py` as one file. Moving policy code and changing its
state behavior together would make failures harder to diagnose. The subsequent
[policy extraction](../POLICY.md) preserves the interface and runner-level tests.
## Lifecycle
@ -103,9 +103,9 @@ use the actual runner and policy together, with isolated game/model boundaries.
## Next boundaries
The state interface now gives a stable boundary for extracting combat and
selection policy without changing execution behavior. Keep a small dispatcher;
no plugin framework or class hierarchy is needed.
Combat and selection policy have since been extracted without changing execution
behavior. See [the current policy structure](../POLICY.md). No plugin framework
or class hierarchy was added.
The next state work is run identity and deck provenance. Recording should then
link observations, proposals, action attempts, results, and reconciliation.