Fix Fable chapter III returning as Saga with lore counter instead of transforming (#2425) - #2598
Conversation
Hydrate DFC back_face at object creation so enter_transformed zone changes can flip Fable to Reflection of Kiki-Jiki instead of silently no-oping and re-applying saga ETB lore-counter replacement.
There was a problem hiding this comment.
Code Review
This pull request resolves an issue where dual-faced cards (DFCs) failed to return to the battlefield transformed (such as Fable of the Mirror-Breaker's Chapter III) by extracting and reusing the back-face hydration logic into populate_back_face_if_dfc. This helper is now integrated into deck loading, scenario setup, and game rehydration, supported by new parser and integration tests. Feedback on these changes highlights a violation of Rule R6 regarding missing mandatory CR annotations on the layout match arms, and suggests a more robust approach of using printed_ref_from_face(card_face) instead of reading from the mutable obj.printed_ref.
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.
| CardLayout::Adventure(_, back) => Some((LayoutKind::Adventure, back)), | ||
| CardLayout::Transform(_, back) => Some((LayoutKind::Transform, back)), | ||
| CardLayout::Modal(_, back) => Some((LayoutKind::Modal, back)), | ||
| CardLayout::Meld(_, back) => Some((LayoutKind::Meld, back)), | ||
| CardLayout::Omen(_, back) => Some((LayoutKind::Omen, back)), | ||
| CardLayout::Prepare(_, back) => Some((LayoutKind::Prepare, back)), |
There was a problem hiding this comment.
[MEDIUM] Missing specific CR annotations on layout match arms. Evidence: crates/engine/src/game/printed_cards.rs:920-925. Why it matters: Rule R6 requires every rules-touching line of engine code to carry a comment of the form CR : . Suggested fix: Add the specific CR comments back to the match arms.
// CR 715: Adventure half available at cast time
CardLayout::Adventure(_, back) => Some((LayoutKind::Adventure, back)),
// CR 712: Transform / Modal DFC / Meld / Omen back face
CardLayout::Transform(_, back) => Some((LayoutKind::Transform, back)),
CardLayout::Modal(_, back) => Some((LayoutKind::Modal, back)),
CardLayout::Meld(_, back) => Some((LayoutKind::Meld, back)),
CardLayout::Omen(_, back) => Some((LayoutKind::Omen, back)),
// CR 702: Prepare (Strixhaven) — face b is the prepare spell
CardLayout::Prepare(_, back) => Some((LayoutKind::Prepare, back)),References
- Rule R6: Every rules-touching line of engine code must carry a comment of the form CR : . (link)
| obj.printed_ref | ||
| .as_ref() | ||
| .and_then(|printed_ref| db.get_other_face_by_printed_ref(printed_ref)) |
There was a problem hiding this comment.
[MEDIUM] Use printed_ref_from_face instead of obj.printed_ref. Evidence: crates/engine/src/game/printed_cards.rs:934-936. Why it matters: Relying on mutable or potentially cleared object state is less robust than using the immutable card_face reference directly. Suggested fix: Use printed_ref_from_face(card_face) to get the printed reference.
| obj.printed_ref | |
| .as_ref() | |
| .and_then(|printed_ref| db.get_other_face_by_printed_ref(printed_ref)) | |
| printed_ref_from_face(card_face) | |
| .as_ref() | |
| .and_then(|printed_ref| db.get_other_face_by_printed_ref(printed_ref)) |
🤖 Architecture Review (automated)Verdict: ✅ Approve w/ nits Seam: PASS — extracting the duplicate-prone inline back_face hydration into a single
Findings
CR numbers verified against |
Restore verified per-layout CR comments in populate_back_face_if_dfc for DFC, adventurer, and preparation layouts. Remove the unused create_object_from_card_face_with_db helper instead of keeping dead public surface area. Verification: cargo fmt --all; git diff --check; pre-commit parser combinator gate. Tilt clippy/test-engine/card-data stayed queued, so GitHub CI is the broad gate.
|
Pushed maintainer-nit follow-up in What changed:
Focused review:
Verification:
|
matthewevans
left a comment
There was a problem hiding this comment.
Maintainer sign-off: reviewed acf05fd. The two review nits are addressed: verified CR comments restored at the layout match arms, and the unused public helper was removed. Architecture remains centered on populate_back_face_if_dfc.
Summary
enter_transformedis a silent no-op whenGameObject::back_faceis unset — the saga ETB lore-counter replacement then fires on the front face.populate_back_face_if_dfc()and calls it during object creation (add_real_card,create_object_from_card_face_with_db) in addition to the existingrehydrate_game_from_card_dbpath.Test plan
cargo test -p engine fable_chaptercargo test -p engine --lib populate_back_face_attachescargo test -p engine --lib fable_chapter_three_exiles