Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 19 additions & 28 deletions crates/engine/src/game/game_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ use serde::{Deserialize, Serialize};

use crate::types::ability::{
additional_cost_instance_payment_count, additional_cost_instance_payment_count_for_ordinal,
AbilityBlockEntry, AbilityDefinition, AdditionalCost, AdditionalCostInstancePayment,
AdditionalCostOrigin, BasicLandType, CastTimingPermission, CastVariantPaid, CastingPermission,
CastingRestriction, ChosenAttribute, ChosenSubtypeKind, CostPaidObjectSnapshot,
ExiledSpellRider, ModalChoice, ReplacementDefinition, SeatDirection, SolveCondition,
SpellCastingOption, StaticDefinition, TriggerBaseSetInstanceRef, TriggerDefinition,
TriggerDefinitionOccurrenceRef, TriggerEntry, TriggerOccurrenceState,
materialize_legacy_printed_trigger_entries, AbilityBlockEntry, AbilityDefinition,
AdditionalCost, AdditionalCostInstancePayment, AdditionalCostOrigin, BasicLandType,
CastTimingPermission, CastVariantPaid, CastingPermission, CastingRestriction, ChosenAttribute,
ChosenSubtypeKind, CostPaidObjectSnapshot, ExiledSpellRider, ModalChoice,
ReplacementDefinition, SeatDirection, SolveCondition, SpellCastingOption, StaticDefinition,
TriggerBaseSetInstanceRef, TriggerDefinition, TriggerDefinitionOccurrenceRef, TriggerEntry,
TriggerOccurrenceState,
};
use crate::types::card::{LayoutKind, PrintedCardRef, PrintedLoyalty, TokenImageRef};
use crate::types::card_type::{CardType, CoreType};
Expand Down Expand Up @@ -1631,32 +1632,22 @@ impl GameObject {
/// runtime payloads are rejected rather than guessed from equal definition
/// bytes.
pub fn migrate_legacy_trigger_definitions(&mut self) -> Result<(), &'static str> {
let has_legacy_entries = self.trigger_definitions.iter_all().any(|entry| {
matches!(
entry.occurrence,
TriggerDefinitionOccurrenceRef::Unmaterialized
)
});
if !has_legacy_entries {
return self.validate_trigger_definitions();
}
if self.base_trigger_definitions.is_empty()
|| self.trigger_definitions.len() != self.base_trigger_definitions.len()
|| !self.trigger_definitions.iter_all().all(|entry| {
matches!(
entry.occurrence,
TriggerDefinitionOccurrenceRef::Unmaterialized
)
})
|| !self
let mut entries = self.trigger_definitions.iter_all().cloned().collect();
materialize_legacy_printed_trigger_entries(
&mut entries,
self.base_trigger_definitions.as_slice(),
self.trigger_base_set_instance,
)?;
if entries
== self
.trigger_definitions
.iter_all()
.zip(self.base_trigger_definitions.iter())
.all(|(entry, base)| entry.definition == *base)
.cloned()
.collect::<Vec<_>>()
{
return Err("legacy runtime trigger payload has no provable producer or base slot");
return self.validate_trigger_definitions();
}
self.materialize_base_trigger_definitions();
self.trigger_definitions = entries.into();
self.validate_trigger_definitions()
}

Expand Down
59 changes: 59 additions & 0 deletions crates/engine/src/types/ability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22152,6 +22152,65 @@ impl TriggerEntry {
}
}

/// Classifies a persisted trigger list without inferring runtime provenance.
/// A list is either wholly legacy payloads or wholly identity-bearing entries;
/// a mixture cannot establish an exact occurrence mapping.
pub(crate) fn legacy_trigger_entry_list(entries: &[TriggerEntry]) -> Result<bool, &'static str> {
let has_legacy = entries.iter().any(|entry| {
matches!(
entry.occurrence,
TriggerDefinitionOccurrenceRef::Unmaterialized
)
});
if has_legacy
&& !entries.iter().all(|entry| {
matches!(
entry.occurrence,
TriggerDefinitionOccurrenceRef::Unmaterialized
)
})
{
return Err("legacy trigger list mixes payload-only and identity-bearing entries");
}
Ok(has_legacy)
}

/// Materializes a payload-only list only when an ordered printed base set proves
/// every slot. Runtime copied and granted triggers have no equivalent proof.
pub(crate) fn materialize_legacy_printed_trigger_entries(
entries: &mut Vec<TriggerEntry>,
base_definitions: &[TriggerDefinition],
base_set: TriggerBaseSetInstanceRef,
) -> Result<(), &'static str> {
if !legacy_trigger_entry_list(entries)? {
return Ok(());
}
if base_definitions.is_empty()
|| entries.len() != base_definitions.len()
|| !entries
.iter()
.zip(base_definitions)
.all(|(entry, base)| entry.definition == *base)
{
return Err("legacy runtime trigger payload has no provable producer or base slot");
}
*entries = base_definitions
.iter()
.cloned()
.enumerate()
.map(|(printed_index, definition)| {
TriggerEntry::new(
TriggerDefinitionOccurrenceRef::Printed {
base_set,
printed_index,
},
definition,
)
})
.collect();
Ok(())
}

#[derive(Deserialize)]
#[serde(untagged)]
enum TriggerEntryWire {
Expand Down
Loading
Loading