feat(@projects/@magic-civilization): add ransom event bridge integration tests

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Natalie 2026-05-09 02:05:04 -07:00
parent 57504e0629
commit 023c6624d2
2 changed files with 78 additions and 1 deletions

View file

@ -25,7 +25,7 @@ Chronicle currently distinguishes "expired-then-captured" from "fresh capture" b
- [x] `TurnResult` gains `ransom_offers_accepted: Vec<UnitRansomAcceptedEvent>` and `ransom_offers_expired: Vec<UnitRansomExpiredEvent>`. ✓ Added at `combat_event.rs:189-198` with `#[serde(default)]` for save migration.
- [x] `mc-turn::process_ransom_expiry` pushes `UnitRansomExpiredEvent` in addition to the existing `UnitCapturedEvent` for the conversion. ✓ `processor.rs:2244-2253` adds the expired event after the capture event.
- [x] `accept_ransom_offer` / `refuse_ransom_offer` push the corresponding event into `pending_capture_events.ransom_offers_accepted` / `_expired` so the next `step()` drains them onto TurnResult. ✓ `api-gdext/src/lib.rs:3791-3804` (accept) and `:3894-3905` (refuse). `PendingCaptureEvents.drain_into` updated at `mc-turn/src/game_state.rs:407-414` to drain both new vecs onto TurnResult.
- [-] ◐ api-gdext bridge surfaces both as `Array[Dictionary]` on the `step()` result. Bridge writes through `pending_capture_events` and they arrive on `TurnResult.ransom_offers_accepted` / `_expired` already; the `step()` Dictionary projection of those Vecs into `Array[Dictionary]` shape is the remaining piece (mirror of `units_captured` dict shape).
- [x] api-gdext bridge surfaces both as `Array[Dictionary]` on the `step()` result. ✓ `api-gdext/src/lib.rs:4317-4344` projects `result.ransom_offers_accepted``step_dict["ransom_offers_accepted"]` as `Array[Dictionary]` (`turn`, `offer_id`, `unit_id`, `captor`, `owner`, `price_paid`); `result.ransom_offers_expired``step_dict["ransom_offers_expired"]` (`turn`, `offer_id`, `unit_id`, `captor`, `prior_owner`). `cargo check --workspace` clean on apricot.
- [ ] godot-engine chronicle subscriber reads from these new arrays directly; cross-reference workaround removed.
- [ ] Test in `mc-turn/tests/ransom.rs` asserts both events appear in `TurnResult` after the corresponding state transitions.

View file

@ -100,3 +100,80 @@ fn tick_drains_only_expired_leaves_others() {
let remaining: Vec<_> = q.iter().map(|o| o.unit_id).collect();
assert_eq!(remaining, vec![102]);
}
// ── p2-55e: integration tests for typed accept/expired events ────────────────
use mc_turn::combat_event::{
UnitRansomAcceptedEvent, UnitRansomExpiredEvent,
};
use mc_turn::game_state::PendingCaptureEvents;
use mc_turn::combat_event::TurnResult;
#[test]
fn pending_capture_events_drain_accept_into_turn_result() {
// p2-55e bullet 4 + bullet 2: pending_capture_events.ransom_offers_accepted
// drains onto TurnResult.ransom_offers_accepted.
let mut pending = PendingCaptureEvents::default();
pending.ransom_offers_accepted.push(UnitRansomAcceptedEvent {
turn: 12,
offer_id: 7,
unit_id: 99,
captor: 1,
owner: 2,
price_paid: 50,
});
let mut result = TurnResult::default();
pending.drain_into(&mut result);
assert!(pending.ransom_offers_accepted.is_empty(),
"drain_into should empty the pending vec");
assert_eq!(result.ransom_offers_accepted.len(), 1);
let ev = &result.ransom_offers_accepted[0];
assert_eq!(ev.offer_id, 7);
assert_eq!(ev.unit_id, 99);
assert_eq!(ev.captor, 1);
assert_eq!(ev.owner, 2);
assert_eq!(ev.price_paid, 50);
}
#[test]
fn pending_capture_events_drain_expired_into_turn_result() {
// p2-55e bullet 3 + bullet 2: pending_capture_events.ransom_offers_expired
// drains onto TurnResult.ransom_offers_expired.
let mut pending = PendingCaptureEvents::default();
pending.ransom_offers_expired.push(UnitRansomExpiredEvent {
turn: 20,
offer_id: 11,
unit_id: 42,
captor: 3,
prior_owner: 4,
});
let mut result = TurnResult::default();
pending.drain_into(&mut result);
assert!(pending.ransom_offers_expired.is_empty());
assert_eq!(result.ransom_offers_expired.len(), 1);
let ev = &result.ransom_offers_expired[0];
assert_eq!(ev.offer_id, 11);
assert_eq!(ev.unit_id, 42);
assert_eq!(ev.captor, 3);
assert_eq!(ev.prior_owner, 4);
}
#[test]
fn pending_capture_events_is_empty_considers_new_vecs() {
// p2-55e: is_empty must consider the new accepted/expired vecs.
let mut pending = PendingCaptureEvents::default();
assert!(pending.is_empty(), "fresh PendingCaptureEvents should be empty");
pending.ransom_offers_accepted.push(UnitRansomAcceptedEvent {
turn: 1, offer_id: 1, unit_id: 1, captor: 0, owner: 1, price_paid: 10,
});
assert!(!pending.is_empty(), "non-empty accepted should report not-empty");
let mut pending = PendingCaptureEvents::default();
pending.ransom_offers_expired.push(UnitRansomExpiredEvent {
turn: 1, offer_id: 1, unit_id: 1, captor: 0, prior_owner: 1,
});
assert!(!pending.is_empty(), "non-empty expired should report not-empty");
}