Skip to content

Combat T096 correction loop ping-pongs on projected-target rejections (64 calls, ~1M tokens in one window) #435

Description

@MoonlightByte

Combat T096 correction loop can ping-pong on projected-target rejections (62 attempts, ~1M tokens in one window)

Observed (marsh probe, 2026-09-19, branch fix/242-down-scene @ aa1dcd1; the code involved is unchanged since August and is the same on main):

Round 2 of a Boggard Marsh fight (Eirik down at 0, Thane 23 HP, Elen 31 HP, two will-o'-wisps and a shambling mound) took 64 T096 calls, 275 s, roughly 1M prompt tokens for one automatic window. Records 5-66 of the T096 capture alternate between two rejections:

target cmb-npc-ranger-thane-1 is already down  -> legalTargets [cmb-npc-scout-elen-1]
target cmb-npc-scout-elen-1 is already down    -> legalTargets [cmb-npc-ranger-thane-1]

What is going on. The validator is right every time. resolve_claimed_window resolves the batch in order against the persisted prerolls; that round the mound held a natural 20 for 34 damage, enough to drop either companion, so whichever companion the enemies focus is at 0 HP by the time the last wisp acts. The model is wrong every time: the correction says "reuse validatedIntents unchanged; change only the rejected actor", but the model answers by moving all enemy attacks onto the named legal target, which the mound's crit then drops instead, and so on. It reasons from the creatures list (start-of-window HP), sees "Elen is already down" with no explanation, and concludes nothing should attack Elen. The loop is unbounded by doctrine and ended only when the model happened to split fire.

Two smaller faults in the same recording compounded it: the mound picked Multiattack (listed on its sheet with 0d0 dice, not executable) in every window, and the companion's Cure Wounds carried requiresPlayerInput for its own 2d8, which pipeline.py honored only for the human actor and otherwise silently committed as a 0 heal while spending the slot.

Fix shipped on fix/242-down-scene (owner-directed: prompt/correction text first, one narrow code reject):

  • _intent_correction: when the rejection is a projected "already down", the correction now explains that the target fell because of the earlier intents in the model's own batch, that creatures shows start-of-window HP, and that redirecting earlier attacks repeats the rejection.
  • T096 system prompt: the same rule in the ordering paragraph; never name Multiattack/0d0 entries as the ability; an NPC never carries requiresPlayerInput (roll its dice in the ruling, supply final hpDelta).
  • pipeline.py: an NPC intent carrying requiresPlayerInput is rejected as a correction instead of committing its declared zero.

Evidence (slice replays against the real resolver, reconstructed round dice, real T096 calls; DEV AID, kit in agent-room-fleet-kit/local-data/242-down-scene/slice_replay.py):

slice before after
D2 corrected call from the recorded rejection resolves in one trip 0/3 3/3
Companion Cure Wounds commits a real heal from the recorded batch 0 (hpDelta 0, slot spent) 3/3, one trip (0->13, 0->12, 0->12)
Fresh round-2 window: Multiattack named / NPC roll request every window 0/4 and 0/4
Human's own roll request still pauses the turn yes yes

Why this issue stays open after the fix. The convergence now rests on the model following the explanation. Code still sends validatedIntents as guidance without enforcing the prefix, so a model that ignores the text can ping-pong again. If it recurs, the deterministic follow-up is to reconcile the validated prefix in code before re-resolving (replace the leading intents by actorId with the validated copies), which makes legalTargets for the rejected actor exact. A per-window attempt cap is not the answer (fail-forward doctrine, #193).

Related: #242 (down scene, where a two-target party makes this trivially reachable), #433 (main-turn correction give-up).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions