Skip to content

fix: preserve revealed card knowledge - #7094

Merged
matthewevans merged 16 commits into
mainfrom
ship/preserve-revealed-card-knowledge
Aug 8, 2026
Merged

fix: preserve revealed card knowledge#7094
matthewevans merged 16 commits into
mainfrom
ship/preserve-revealed-card-knowledge

Conversation

@matthewevans

@matthewevans matthewevans commented Aug 8, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features

    • Card identities learned through reveals, peeks, searches, and similar effects now remain visible to the appropriate viewer.
    • Previously revealed cards are displayed correctly in opponents’ hands and libraries.
    • Known cards are preserved during game-state updates and AI decision-making.
  • Bug Fixes

    • Improved delayed-trigger handling so effects use the correct source information.
    • Fixed card-knowledge expiration when libraries are shuffled, reordered, or otherwise changed.
    • Corrected visibility handling for private card information.

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@matthewevans, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 12 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: fe797e1c-8f73-4cd2-8cd0-53fb24d6c6cd

📥 Commits

Reviewing files that changed from the base of the PR and between a4d7fc5 and cb18700.

📒 Files selected for processing (27)
  • client/src/adapter/types.ts
  • client/src/components/hand/OpponentHand.tsx
  • client/src/components/hand/__tests__/OpponentHand.test.tsx
  • client/src/components/zone/LibraryPile.tsx
  • client/src/components/zone/ZoneViewer.tsx
  • client/src/components/zone/__tests__/LibraryPile.test.tsx
  • client/src/components/zone/__tests__/ZoneViewer.test.tsx
  • client/src/components/zone/__tests__/issue_2889_hideaway_exile_visibility.test.tsx
  • client/src/viewmodel/__tests__/gameStateView.test.ts
  • client/src/viewmodel/gameStateView.ts
  • crates/engine/src/database/card_db.rs
  • crates/engine/src/game/ability_scan.rs
  • crates/engine/src/game/casting_costs.rs
  • crates/engine/src/game/derived_views.rs
  • crates/engine/src/game/effects/delayed_trigger.rs
  • crates/engine/src/game/engine.rs
  • crates/engine/src/game/engine_resolution_choices.rs
  • crates/engine/src/game/game_object.rs
  • crates/engine/src/game/triggers.rs
  • crates/engine/src/game/visibility.rs
  • crates/engine/src/types/game_state.rs
  • crates/engine/tests/integration/deterministic_game_state_serde.rs
  • crates/engine/tests/integration/integration_bending.rs
  • crates/engine/tests/integration/issue_3263_gitaxian_probe.rs
  • crates/phase-ai/src/determinize.rs
  • crates/phase-ai/src/search.rs
  • scripts/migrate-mana-target-roles.mjs
📝 Walkthrough

Walkthrough

The PR adds durable viewer-specific card knowledge across the engine, client, and AI determinization. It separates delayed-trigger provenance by condition type, migrates card fixtures to deterministic gzip files, and adds Earthbend and Gitaxian Probe regression coverage.

Changes

Durable Card Knowledge

Layer / File(s) Summary
Product knowledge state
crates/engine/src/types/game_state.rs
GameState stores viewer-specific card identity facts and library knowledge epochs.
Knowledge capture and invalidation
crates/engine/src/game/effects/*, crates/engine/src/game/engine.rs, crates/engine/src/game/library.rs, crates/engine/src/game/zones.rs
Reveal, peek, search, and Scry paths record identities. Library reorder and shuffle operations advance knowledge epochs.
Viewer projection and consumers
crates/engine/src/game/visibility.rs, client/src/..., crates/phase-ai/src/determinize.rs
Viewer projections preserve authorized known identities. Client rendering and AI determinization consume the projected knowledge.
Knowledge regression coverage
crates/engine/tests/integration/issue_3263_gitaxian_probe.rs
An integration test verifies private card knowledge through casting, resolution, and subsequent hand changes.

Delayed-Trigger Provenance

Layer / File(s) Summary
Delayed-trigger provenance
crates/engine/src/game/effects/delayed_trigger.rs
Phase-delayed conditions use creation-time source snapshots. Event-delayed conditions resolve provenance from the firing event, with exhaustive classification tests.

Compressed Card Fixtures

Layer / File(s) Summary
Reader-based fixture loading
crates/engine/src/database/card_db.rs, crates/engine/src/test_support.rs, crates/engine/tests/integration/*, crates/phase-ai/src/search.rs
Card database loading accepts readers, and test loaders decompress integration_cards.json.gz before parsing.
Canonical fixture generation
scripts/gen-test-fixture.py, scripts/migrate-mana-target-roles.mjs
Fixture generation and migration use deterministic gzip output and decompressed validation. The migration adds Jeska’s Will and Jetfire role mappings.
Fixture path updates
crates/engine/src/game/*, crates/engine/src/parser/oracle_tests.rs, crates/engine/tests/integration/*, scripts/check-test-card-data-load.sh
Fixture references now use the compressed .json.gz path.

Earthbend Regression

Layer / File(s) Summary
Earthbend scenario coverage
crates/engine/tests/integration/integration_bending.rs
A real-card integration test covers Earthbend animation, sacrifice, tapped return, Plant creation, and stack settlement.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GameAction
  participant GameState
  participant VisibilityProjection
  participant OpponentHand
  GameAction->>GameState: disclose or inspect card identities
  GameState->>GameState: record viewer-specific knowledge
  GameState->>VisibilityProjection: project known card IDs
  VisibilityProjection->>OpponentHand: provide viewer_known_card_ids
  OpponentHand->>OpponentHand: render known card faces
Loading

Possibly related PRs

Suggested labels: bug

Suggested reviewers: lgray, parthmishra

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary change: preserving revealed card knowledge across game state and viewer filtering.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch ship/preserve-revealed-card-knowledge
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ship/preserve-revealed-card-knowledge

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@matthewevans
matthewevans enabled auto-merge August 8, 2026 04:33
@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown

Generated for head cb1870047b20bb6a2be5a926589c03e9ec1ee1e7.

Parse changes introduced by this PR

✓ No card-parse changes detected.

@matthewevans
matthewevans force-pushed the ship/preserve-revealed-card-knowledge branch from 36a04fc to a4d7fc5 Compare August 8, 2026 12:11

@coderabbitai coderabbitai 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.

Actionable comments posted: 10

🧹 Nitpick comments (4)
crates/engine/src/game/engine.rs (1)

5570-5578: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add CR citations to the new knowledge-capture blocks.

Both new blocks record durable card knowledge but carry no CR NNN citation, unlike most of the surrounding code in this file. Add a short comment naming the CR section that grounds each block (for example, the general reveal/visibility rules for the public-reveal block, and CR 701.22a for the scry-disclosure block), so the rule basis is verifiable at a glance.

As per path instructions, rules-touching code without a verified CR <number>: <description> annotation is a finding for crates/engine/**.

Also applies to: 6776-6790

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/engine/src/game/engine.rs` around lines 5570 - 5578, Add verified CR
citations to both durable knowledge-capture blocks: annotate the public-reveal
block around state.remember_card_identities with the applicable general
reveal/visibility rule, and annotate the scry-disclosure block around lines
6776-6790 with CR 701.22a. Use concise comments in the required “CR <number>:
<description>” format without changing behavior.

Source: Path instructions

crates/engine/src/game/effects/scoped_library_search.rs (1)

555-562: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add a CR citation for this knowledge-capture block.

This block records durable card identities for the search's learned audience. Other new knowledge-capture code in this PR (for example remember_public_reveals in engine.rs) documents its rules basis in prose. Add a short comment here that states which CR section grounds the audience/visibility rule this block implements (for example CR 701.23a and CR 400.7), so a future reader can verify the code against the rule.

As per path instructions, "rules-touching code with no verified CR <number>: <description> annotation" is a finding for crates/engine/**.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/engine/src/game/effects/scoped_library_search.rs` around lines 555 -
562, Add a concise rules-basis comment immediately above the knowledge-capture
block in the active search flow, identifying the applicable CR sections (such as
CR 701.23a and CR 400.7) and describing the audience/visibility rule it
implements. Keep the existing looked_at collection, remember_card_identities
call, and active search insertion unchanged.

Source: Path instructions

crates/engine/src/database/card_db.rs (1)

80-83: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Buffer the reader inside from_export_reader to avoid single-byte reads through the decompression stream.

serde_json::from_reader decodes through Read::bytes(). That iterator calls self.inner.read() once per byte, regardless of whether R is buffered. The encoding used for all stdio inputs and outputs. Default: 'buffer'. is unrelated here, but the general point stands from Rust's own std::io::Read::bytes() contract: it does not batch reads on your behalf.

Every gzip-backed fixture loader in this PR (test_support.rs, the_fourteenth_doctor_graveyard_copy.rs, and four call sites in search.rs) passes a bare GzDecoder<BufReader<File>> into this function. The outer GzDecoder does not implement BufRead, so every decompressed byte triggers a separate call into the decompression state machine.

Wrap reader in an internal BufReader so every current and future caller gets buffered reads automatically, without changing the public signature or touching any call site.

⚡ Proposed fix
     pub fn from_export_reader<R: Read>(reader: R) -> Result<Self, Box<dyn std::error::Error>> {
-        let entries: HashMap<String, CardExportEntry> = serde_json::from_reader(reader)?;
+        let entries: HashMap<String, CardExportEntry> = serde_json::from_reader(BufReader::new(reader))?;
         Ok(Self::from_export_entries(entries))
     }

Since I can't execute Rust in this sandbox, please confirm that serde_json::from_reader still buffers this way in the pinned serde_json version and that double-wrapping an already-buffered BufReader<File> (from from_export) causes no behavior change.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/engine/src/database/card_db.rs` around lines 80 - 83, Update
from_export_reader to wrap the generic reader in an internal std::io::BufReader
before passing it to serde_json::from_reader. Keep the public signature and
existing call sites unchanged, preserving behavior for already-buffered readers
and gzip-backed readers.
crates/phase-ai/src/search.rs (1)

4709-4717: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider extracting a shared fixture-loading helper for this test module.

This block, and the three identical blocks at lines 7242-7248, 7437-7443, and 7600-7606, all open the same .json.gz path and repeat the same BufReader/GzDecoder/from_export_reader sequence. Extract one private helper function in this module and call it from all four tests to remove the duplication.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/phase-ai/src/search.rs` around lines 4709 - 4717, The four tests
duplicate loading the integration_cards.json.gz fixture. Add a private
fixture-loading helper in the test module that performs the Path, File,
BufReader, GzDecoder, and CardDatabase::from_export_reader sequence, then
replace the duplicated setup in
prospective_fetch_choice_survives_to_the_real_search_prompt and the three
corresponding tests with calls to that helper.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@client/src/components/hand/OpponentHand.tsx`:
- Line 83: Remove the client-side viewerKnownCardIds visibility check from
OpponentHand and render the engine-projected display visibility property
instead. Add the projection in the engine-to-client adapter, preserve it through
the reverse adapter, and add round-trip coverage confirming the property
survives conversion.

In `@client/src/viewmodel/gameStateView.ts`:
- Line 159: Remove the viewer_known_card_ids-based visibility/reportability
checks from the gameStateView logic, including the branches near the referenced
condition and lines 235-239. Use the engine-projected per-object visibility
result as the sole authority for rendering, without deriving visibility from
card identities in the frontend.

In `@crates/engine/src/game/effects/delayed_trigger.rs`:
- Around line 397-412: Add a verified “CR <number>: <description>” annotation to
the documentation for condition_uses_creation_time_provenance, citing the rule
that phase-delayed conditions retain creation-time object context while
event-delayed conditions resolve provenance from the firing event. Keep the
classifier behavior unchanged.
- Around line 1372-1413: Add a regression test alongside
only_phase_delayed_conditions_use_creation_time_provenance that creates a
WhenNextEvent delayed trigger during event A, then fires it via event B through
the production trigger and ability-resolution pipeline. Assert the resolved
effect uses B’s TriggeringSource and does not retain A’s destination-zone
origin, exercising the prevented failure path rather than calling
condition_uses_creation_time_provenance directly.

In `@crates/engine/src/game/engine_resolution_choices.rs`:
- Line 1567: Update the direct library reorder paths in SurveilChoice’s
reorder_within_library flow and DigChoice when kept_destination is
Some(Zone::Library) to call state.advance_library_knowledge_epoch(player) after
cards are placed. Preserve the existing behavior for other destinations and
ensure each direct keep-on-top mutation invalidates prior library ordering
knowledge.

In `@crates/engine/src/game/engine.rs`:
- Around line 5570-5578: Update the public-reveal knowledge update at the event
boundary to pass unpublished to state.remember_card_identities instead of the
full card_ids collection. Preserve the existing audience construction and ensure
controller-only occurrences excluded by resolve_and_apply_information remain
hidden from other players’ durable product knowledge.

In `@crates/engine/src/types/game_state.rs`:
- Around line 6296-6304: Update GameState::advance_library_knowledge_epoch to
invalidate or canonicalize library product facts when advancing the epoch, so
stale knowledge cannot affect loop-state equality. At the GameState::eq
comparison site, compare only current product knowledge or apply the same
normalization while preserving existing loop-state equality semantics; both
referenced locations in crates/engine/src/types/game_state.rs require
coordinated handling.

In `@crates/engine/tests/integration/integration_bending.rs`:
- Around line 164-174: Update the activation selection around sacrifice_ability
to stop matching AbilityCost internals; use the available typed test helper or a
stable printed ability identity for Yawgmoth’s intended sacrifice ability, while
preserving the assertion that the expected ability is present.

In `@crates/phase-ai/src/determinize.rs`:
- Around line 148-154: Extend the determinization regression tests around
remember_card_identities and viewer_knows_card_identity to exercise the failure
path: record identities for the AI viewer, verify known opposing cards remain
pinned while unknown cards resample, and verify library knowledge expires after
a reorder. Ensure the test explicitly invokes the relevant knowledge APIs and
fails without the durable pinning fix.

In `@scripts/migrate-mana-target-roles.mjs`:
- Around line 92-112: Update writeCanonicalGzip to keep gzipCommand’s stdout as
a raw Buffer when writing the compressed fixture, removing the UTF-8 output
encoding from that invocation. Preserve the existing UTF-8 decoding exclusively
in readGzipUtf8, where decompressed text is consumed.

---

Nitpick comments:
In `@crates/engine/src/database/card_db.rs`:
- Around line 80-83: Update from_export_reader to wrap the generic reader in an
internal std::io::BufReader before passing it to serde_json::from_reader. Keep
the public signature and existing call sites unchanged, preserving behavior for
already-buffered readers and gzip-backed readers.

In `@crates/engine/src/game/effects/scoped_library_search.rs`:
- Around line 555-562: Add a concise rules-basis comment immediately above the
knowledge-capture block in the active search flow, identifying the applicable CR
sections (such as CR 701.23a and CR 400.7) and describing the
audience/visibility rule it implements. Keep the existing looked_at collection,
remember_card_identities call, and active search insertion unchanged.

In `@crates/engine/src/game/engine.rs`:
- Around line 5570-5578: Add verified CR citations to both durable
knowledge-capture blocks: annotate the public-reveal block around
state.remember_card_identities with the applicable general reveal/visibility
rule, and annotate the scry-disclosure block around lines 6776-6790 with CR
701.22a. Use concise comments in the required “CR <number>: <description>”
format without changing behavior.

In `@crates/phase-ai/src/search.rs`:
- Around line 4709-4717: The four tests duplicate loading the
integration_cards.json.gz fixture. Add a private fixture-loading helper in the
test module that performs the Path, File, BufReader, GzDecoder, and
CardDatabase::from_export_reader sequence, then replace the duplicated setup in
prospective_fetch_choice_survives_to_the_real_search_prompt and the three
corresponding tests with calls to that helper.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3928fec5-1da0-41b4-b78c-b10987ff642d

📥 Commits

Reviewing files that changed from the base of the PR and between b42dcb1 and a4d7fc5.

⛔ Files ignored due to path filters (1)
  • crates/engine/tests/fixtures/integration_cards.json.gz is excluded by !**/*.gz
📒 Files selected for processing (35)
  • client/src/adapter/types.ts
  • client/src/components/hand/OpponentHand.tsx
  • client/src/viewmodel/gameStateView.ts
  • crates/engine/src/database/card_db.rs
  • crates/engine/src/game/ability_scan.rs
  • crates/engine/src/game/casting_tests.rs
  • crates/engine/src/game/effects/delayed_trigger.rs
  • crates/engine/src/game/effects/dig.rs
  • crates/engine/src/game/effects/manifest_dread.rs
  • crates/engine/src/game/effects/reveal_hand.rs
  • crates/engine/src/game/effects/scoped_library_search.rs
  • crates/engine/src/game/engine.rs
  • crates/engine/src/game/engine_exile_return_tests.rs
  • crates/engine/src/game/engine_resolution_choices.rs
  • crates/engine/src/game/library.rs
  • crates/engine/src/game/visibility.rs
  • crates/engine/src/game/zones.rs
  • crates/engine/src/parser/oracle_tests.rs
  • crates/engine/src/test_support.rs
  • crates/engine/src/types/game_state.rs
  • crates/engine/tests/fixtures/integration_cards.json
  • crates/engine/tests/integration/bards_company_recruit.rs
  • crates/engine/tests/integration/heist_production_path_handoff.rs
  • crates/engine/tests/integration/integration_bending.rs
  • crates/engine/tests/integration/issue_3263_gitaxian_probe.rs
  • crates/engine/tests/integration/issue_6691_enters_under_their_control.rs
  • crates/engine/tests/integration/loop_shortcut_mana_engine.rs
  • crates/engine/tests/integration/support.rs
  • crates/engine/tests/integration/the_fourteenth_doctor_graveyard_copy.rs
  • crates/engine/tests/integration/token_zone_change_index.rs
  • crates/phase-ai/src/determinize.rs
  • crates/phase-ai/src/search.rs
  • scripts/check-test-card-data-load.sh
  • scripts/gen-test-fixture.py
  • scripts/migrate-mana-target-roles.mjs

Comment thread client/src/components/hand/OpponentHand.tsx Outdated
Comment thread client/src/viewmodel/gameStateView.ts Outdated
Comment thread crates/engine/src/game/effects/delayed_trigger.rs Outdated
Comment thread crates/engine/src/game/effects/delayed_trigger.rs
Comment thread crates/engine/src/game/engine_resolution_choices.rs
Comment thread crates/engine/src/game/engine.rs Outdated
Comment thread crates/engine/src/types/game_state.rs
Comment thread crates/engine/tests/integration/integration_bending.rs Outdated
Comment thread crates/phase-ai/src/determinize.rs
Comment thread scripts/migrate-mana-target-roles.mjs
@matthewevans
matthewevans force-pushed the ship/preserve-revealed-card-knowledge branch from 8ad9011 to 5bc43b5 Compare August 8, 2026 13:30
@matthewevans
matthewevans added this pull request to the merge queue Aug 8, 2026
Merged via the queue into main with commit 117b430 Aug 8, 2026
17 checks passed
@matthewevans
matthewevans deleted the ship/preserve-revealed-card-knowledge branch August 8, 2026 14:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant