Skip to content

Add Opposition Agent#5888

Open
parthmishra wants to merge 8 commits into
phase-rs:mainfrom
parthmishra:codex/opposition-agent
Open

Add Opposition Agent#5888
parthmishra wants to merge 8 commits into
phase-rs:mainfrom
parthmishra:codex/opposition-agent

Conversation

@parthmishra

@parthmishra parthmishra commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds engine support for Opposition Agent.

Files changed

  • crates/engine/src/analysis/
  • crates/engine/src/game/
  • crates/engine/src/parser/
  • crates/engine/src/types/
  • crates/engine/tests/integration/opposition_agent.rs
  • crates/phase-ai/src/policies/

CR references

  • CR 701.23a
  • CR 614.1
  • CR 614.6
  • CR 616.1
  • CR 611.3d
  • CR 723.1a
  • CR 305.1
  • CR 601.3
  • CR 609.4b

Implementation method (required)

Method: /engine-implementer

Track

Developer

LLM

Model: gpt-5
Thinking: high

Tier: Standard

Verification

  • Required checks ran clean, or the exact CI-owned alternative is stated below.

  • Gate A output below is for the current committed head.

  • Final review-impl below is clean for the current committed head.

  • Both anchors cite existing analogous code at the same seam.

  • cargo fmt --all — passed on the final committed head.

  • Parser Gate G and Gate A — passed on the final committed head.

  • Final review-impl — passed on the final committed head.

  • Tilt clippy — the narrowed task diff reached terminal ok before the final upstream-only sync; the exact-head rerun was blocked by the local filesystem reaching No space left on device, so exact-head completion is CI-owned.

  • Tilt card-data — exact-head rerun was blocked by the same local No space left on device condition; completion is CI-owned.

  • cargo coverage — earlier task snapshot exited 0; 31,351/35,397 cards supported (88.57%).

  • cargo semantic-audit — earlier task snapshot exited 0; 32,396 cards audited.

  • Tilt test-engine and test-ai — full final-head completion is CI-owned.

  • Strict no-MTGish path gate — passed for branch, index, worktree, and untracked files on the final committed head.

  • Validator-scope gate — passed; the four database/export files called out in review are absent from the PR diff, along with the introduced raw keyword iteration sites.

Gate A

Gate A PASS head=93b61c791a09ffc5b37e0317b390e7c31cfcbb07 base=7403be8a5e14c33e3e416531a45497187d32aa31

Anchored on

  • crates/engine/src/parser/oracle_replacement.rs:1270 — existing nom-based replacement parser that lowers a complete Oracle pattern into a typed ReplacementDefinition with an execute ability.
  • crates/engine/src/parser/oracle_replacement.rs:1385 — existing nom-based replacement parser that composes subject and consequence alternatives into typed replacement parameters.

Final review-impl

Final review-impl PASS head=93b61c791a09ffc5b37e0317b390e7c31cfcbb07

Claimed parse impact

  • Opposition Agent

Validation Failures

The final local clippy and card-data reruns could not complete because the workspace filesystem had only hundreds of MB free and Rust failed writes with No space left on device. Repository policy forbids deleting Tilt build artifacts while Tilt is active, so those exact-head checks are CI-owned. No code diagnostic remains known.

CI Failures

The complete final-head clippy, card-data, test-engine, and test-ai checks are CI-owned due the local storage constraint. A prior intermediate test run observed the unrelated token-preset expected-count mismatch caused by an uncommitted crates/engine/data/known-tokens.toml change in the shared workspace; that file is excluded from this PR.

@github-actions github-actions Bot added the needs-maintainer AI-contribution PR requires human triage (Non-dev track or unresolved gaps) label Jul 15, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements comprehensive support for Opposition Agent's mechanics, including search-control, found-card replacement, exile play permissions, and nested replacement resume. The changes introduce the SearchFound replacement event, track library search control, manage turn control lifecycles, and update the parser to support these patterns. The review feedback highlights several critical areas for improvement: correcting a stable-sorting contradiction in turn control migration where max_by_key behavior conflicts with vector insertion order, optimizing a double lookup on state.objects, and ensuring strict adherence to the repository style guide by adding mandatory CR annotations to rules-touching functions and replacing the reveal boolean field with a typed enum. Additionally, the reviewer advises against swallowing unexpected errors during batch completion.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

return;
};
if let Ok(batch) = process_search_found_batch(state, batch, events) {
let _ = finish_search_found_batch(state, batch, events);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The EngineError returned by finish_search_found_batch is silently swallowed here using let _ =. Swallowing unexpected errors can mask critical bugs and leave the engine in an invalid state. We should propagate or expect the result, aligning with other batch completion handlers in run_batch_completion.

        finish_search_found_batch(state, batch, events)
            .expect("SearchFound batch completion must resolve");
References
  1. Avoid swallowing all errors (e.g., using .ok()) when only specific errors are expected to be handled or ignored. Propagate unexpected errors (such as invariant violations or invalid actions) to prevent masking critical bugs.

Comment on lines +37 to +50
// Legacy saves have no creation timestamp. Zero is the deterministic oldest
// timestamp; insertion at the front makes any other timestamp-zero legacy
// schedule later in vector order win the stable max-by-key tie.
state.scheduled_turn_controls.insert(
0,
ScheduledTurnControl {
target_player,
controller,
timestamp: 0,
lifecycle: ScheduledTurnControlLifecycle::Active,
grant_extra_turn_after: false,
window: crate::types::ability::ControlWindow::NextTurn,
},
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Rust's Iterator::max_by_key returns the first element that achieves the maximum value. Since the newly materialized legacy entry is inserted at the front (index 0), it will actually win any tie with other timestamp-zero entries, directly contradicting the comment's claim that 'any other timestamp-zero legacy schedule later in vector order' would win. Pushing the entry to the end of the vector instead of inserting it at the front resolves this contradiction and is also more efficient.

    // Legacy saves have no creation timestamp. Zero is the deterministic oldest
    // timestamp; pushing to the end makes any other timestamp-zero legacy
    // schedule earlier in vector order win the stable max-by-key tie.
    state.scheduled_turn_controls.push(
        ScheduledTurnControl {
            target_player,
            controller,
            timestamp: 0,
            lifecycle: ScheduledTurnControlLifecycle::Active,
            grant_extra_turn_after: false,
            window: crate::types::ability::ControlWindow::NextTurn,
        },
    );

Comment on lines +194 to +199
if state
.objects
.get(&object_id)
.is_some_and(|object| object.zone == Zone::Exile)
{
if let Some(object) = state.objects.get_mut(&object_id) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The function grant_search_found_play_permission performs a double lookup on state.objects by calling get and then get_mut for the same object_id. This can be optimized into a single get_mut call to improve efficiency.

    if let Some(object) = state.objects.get_mut(&object_id) {
        if object.zone == Zone::Exile {

Comment on lines +138 to +141
fn library_search_control_effect(
state: &GameState,
searcher: PlayerId,
) -> Option<PlayerControlEffect> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The rules-touching function library_search_control_effect lacks a mandatory CR <number>: <description> comment, violating Rule R6 of the repository style guide. Please add an appropriate CR annotation (e.g., referencing CR 723.1a or CR 723.5).

References
  1. Every rules-touching line of engine code must carry a comment of the form CR : . (link)

Comment thread crates/engine/src/types/game_state.rs Outdated
Comment on lines +1576 to +1577
#[serde(default)]
pub reveal: bool,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The struct PendingSearchFoundBatch introduces a new bool field reveal, violating Rule R2 of the repository style guide ('No bool fields — parameterize with existing typed enums'). Please replace this with a typed enum (e.g., RevealMode or similar) to better express the design space.

References
  1. No bool fields — parameterize with existing typed enums. (link)

Comment on lines +296 to +301
pub(crate) fn complete_replaced_selection(
state: &mut GameState,
player: PlayerId,
survivors: Vec<ObjectId>,
events: &mut Vec<GameEvent>,
) -> Result<(), EffectError> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The rules-touching function complete_replaced_selection lacks a mandatory CR <number>: <description> comment, violating Rule R6 of the repository style guide. Please add an appropriate CR annotation (e.g., referencing CR 701.23a).

References
  1. Every rules-touching line of engine code must carry a comment of the form CR : . (link)

@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown

Parse changes introduced by this PR

Baseline pending for 7403be8a5e14c33e3e416531a45497187d32aa31 — this populates once main publishes its coverage snapshot (a few minutes after that commit landed).

@matthewevans matthewevans self-assigned this Jul 16, 2026
@matthewevans matthewevans added the bug Bug fix label Jul 16, 2026

@matthewevans matthewevans left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request changes — narrow this card-specific PR and use the engine authority.

🔴 Blocker

[HIGH] The change adds 5,465 unrelated validation lines from crates/engine/src/database/card_db.rs:72 through the generic validator beginning near :410 to a one-card Opposition Agent PR. Why it matters: the unrelated blast radius cannot be reviewed or safely attributed to this fix. Suggested fix: split the generic validator work out and retain only the scoped Opposition Agent change.

[HIGH] The CI Engine authority gate fails on raw .keywords.iter() uses at crates/engine/src/database/card_db.rs:418,699,1222,2790. Why it matters: the PR violates the repository's keyword-access authority. Suggested fix: route those checks through the established authority rather than direct iteration.

Recommendation: request-changes.

@matthewevans matthewevans removed their assignment Jul 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Bug fix needs-maintainer AI-contribution PR requires human triage (Non-dev track or unresolved gaps)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants