fix(policy): reconcile session-owned action memory with game observations
This commit is contained in:
parent
3f243eaeee
commit
bf41945ef9
9 changed files with 405 additions and 362 deletions
25
run.py
25
run.py
|
|
@ -365,6 +365,9 @@ def main() -> int:
|
|||
duplicate_waits = 0
|
||||
jev_errors = 0
|
||||
saw_a_run = False
|
||||
policy_context = brain.PolicyContext()
|
||||
pending_action = None
|
||||
pending_since = time.monotonic()
|
||||
|
||||
for step in range(1, args.steps + 1):
|
||||
try:
|
||||
|
|
@ -428,9 +431,9 @@ def main() -> int:
|
|||
print(json.dumps(obs, indent=2)[:900])
|
||||
return finish(1, "stuck")
|
||||
|
||||
# Only wait when our own action actually landed and the game is still
|
||||
# animating. If the action was rejected, fall through and pick a
|
||||
# different one instead of waiting out the stuck counter.
|
||||
# Acceptance does not prove completion. The policy context handles
|
||||
# pending selections and purchases. Rejections allow another proposal
|
||||
# instead of waiting out the stuck counter.
|
||||
#
|
||||
# NOTE: this check now happens AFTER deciding, and only suppresses a
|
||||
# REPEATED action. Waiting on "state unchanged" alone blocked
|
||||
|
|
@ -458,7 +461,7 @@ def main() -> int:
|
|||
# must not inherit the previous step's answers in the log.
|
||||
client.last = None
|
||||
try:
|
||||
decision = brain.decide(obs, client, deck_snapshot)
|
||||
decision = brain.decide(obs, client, deck_snapshot, context=policy_context)
|
||||
# A procedural action or animation wait does not establish model
|
||||
# recovery. Only a successful model response resets the budget.
|
||||
if client is not None and client.last is not None:
|
||||
|
|
@ -478,7 +481,7 @@ def main() -> int:
|
|||
# the same failing request -- measured, a DNS blip re-raised out of
|
||||
# the "fallback" and killed the session.
|
||||
try:
|
||||
decision = brain.decide(obs, None, deck_snapshot)
|
||||
decision = brain.decide(obs, None, deck_snapshot, context=policy_context)
|
||||
except Exception as inner: # noqa: BLE001
|
||||
trace({"step": step, "event": "fallback_error", "error": str(inner)[:160]})
|
||||
print(f"[{step:03d}] fallback also failed: {inner}")
|
||||
|
|
@ -499,6 +502,13 @@ def main() -> int:
|
|||
print(json.dumps(obs, indent=2)[:800])
|
||||
return finish(1, "no_decision")
|
||||
|
||||
# Bound unresolved actions even if unrelated observation fields change.
|
||||
if policy_context.pending is not pending_action:
|
||||
pending_action = policy_context.pending
|
||||
pending_since = time.monotonic()
|
||||
if pending_action is not None and time.monotonic() - pending_since > args.stuck_seconds:
|
||||
return finish(1, "pending_action_timeout")
|
||||
|
||||
# Between turns there is nothing to do but look again.
|
||||
if decision.action == "__wait__":
|
||||
waits += 1
|
||||
|
|
@ -508,8 +518,8 @@ def main() -> int:
|
|||
|
||||
# Suppress DUPLICATE actions on an unchanged state, but only for a
|
||||
# bounded number of reads. Proposing a DIFFERENT action is always
|
||||
# allowed, which is what makes select-then-embark and multi-purchase
|
||||
# shops work. The bound matters because some actions legitimately need
|
||||
# allowed here, but cannot bypass a pending policy action above.
|
||||
# The bound matters because some actions legitimately need
|
||||
# repeating (multi-line Ancient dialogue) and some transitions are just
|
||||
# slow -- waiting forever on those stalls the run.
|
||||
action_key = (decision.action, json.dumps(decision.params, sort_keys=True))
|
||||
|
|
@ -543,6 +553,7 @@ def main() -> int:
|
|||
"action": decision.action, "error": str(exc)[:200]})
|
||||
return finish(1, "action_error")
|
||||
|
||||
policy_context.record_result(obs, decision, accepted=result.ok)
|
||||
if not result.ok:
|
||||
print(f"[{step:03d}] action rejected: {result.message}")
|
||||
trace({"step": step, "event": "action_rejected",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue