feat(phase2): observation-only LLMPolicy entry (D1) + pinned schema_version (P0.1)
- LLMPolicy is now Callable[[Observation], tuple[Action, ...]]: the snapshot is the policy's sole information source; the live CombatantState is no longer passed to new-style policies. - CombatEngine._llm_adapter keeps a compatibility path for pre-D1 two-argument callables: the signature is inspected ONCE at wrap time (_accepts_state) and legacy policies still receive (observation, state). - Observation.to_dict() carries schema_version (OBSERVATION_SCHEMA_VERSION=1) as its first pinned key; bump only on removal/meaning change. - Pinned keyset tests updated (test_observation, test_observation_enriched); new tests: observation-only e2e, legacy 2-arg e2e through the adapter, schema_version pin + JSON-serializability. - README: documents the new entry signature and the schema_version key.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"""Package U8 — final validation: an observation-only LLM policy drives the
|
||||
Phase-2 capabilities (U1-U7b) end-to-end through the real engine.
|
||||
|
||||
Each test wires a ``llm_policy`` (``LLMPolicy(observation, state)``) that
|
||||
Each test wires a ``llm_policy`` (``LLMPolicy(observation)``) that
|
||||
reads ONLY the frozen Observation snapshot — never ``engine.*`` — and emits
|
||||
one newly-unblocked tactical decision per capability: ``ready`` (U4),
|
||||
``delay`` (U3), ``total_defense`` (U5a), ``move`` with an explicit
|
||||
@@ -121,7 +121,7 @@ def _counting(decide: Decide) -> LLMPolicy:
|
||||
"""LLMPolicy calling ``decide(id, call_no, obs)`` per turn (obs only)."""
|
||||
calls: dict[str, int] = {}
|
||||
|
||||
def policy(obs: Observation, _state: CombatantState) -> tuple[Action, ...]:
|
||||
def policy(obs: Observation) -> tuple[Action, ...]:
|
||||
cid = str(_me(obs)["id"])
|
||||
n = calls.get(cid, 0)
|
||||
calls[cid] = n + 1
|
||||
|
||||
Reference in New Issue
Block a user