Broaden chairman target scope event support

This commit is contained in:
Jan Petykiewicz 2026-04-16 18:03:17 -07:00
commit a63de904fa
15 changed files with 1257 additions and 30 deletions

View file

@ -1161,6 +1161,7 @@ fn runtime_packed_event_grouped_effect_row_summary_from_smp(
target_mask_bits: row.target_mask_bits,
parameter_family: row.parameter_family.clone(),
grouped_target_subject: row.grouped_target_subject.clone(),
grouped_target_scope: row.grouped_target_scope.clone(),
opcode: row.opcode,
raw_scalar_value: row.raw_scalar_value,
value_byte_0x09: row.value_byte_0x09,
@ -1279,6 +1280,7 @@ fn lowered_record_decoded_actions(
if let Some(blocker) = packed_record_condition_scope_import_blocker(record, company_context) {
return Err(blocker);
}
ensure_condition_true_chairman_context(record)?;
let lowered_company_target = lowered_condition_true_company_target(record)?;
let lowered_player_target = lowered_condition_true_player_target(record)?;
@ -1582,6 +1584,18 @@ fn lower_condition_targets_in_effect(
)?,
value: *value,
},
RuntimeEffect::SetCompanyGovernanceScalar {
target,
metric,
value,
} => RuntimeEffect::SetCompanyGovernanceScalar {
target: lower_condition_true_company_target_in_company_target(
target,
lowered_company_target,
)?,
metric: *metric,
value: *value,
},
RuntimeEffect::SetChairmanCash { target, value } => RuntimeEffect::SetChairmanCash {
target: target.clone(),
value: *value,
@ -1912,6 +1926,23 @@ fn lower_condition_true_player_target_in_player_target(
}
}
fn ensure_condition_true_chairman_context(
record: &SmpLoadedPackedEventRecordSummary,
) -> Result<(), ImportBlocker> {
if !record_uses_condition_true_chairman(record) {
return Ok(());
}
if record
.decoded_conditions
.iter()
.any(|condition| matches!(condition, RuntimeCondition::ChairmanNumericThreshold { .. }))
{
Ok(())
} else {
Err(ImportBlocker::MissingConditionContext)
}
}
fn lower_territory_target_in_condition(
target: &RuntimeTerritoryTarget,
row: &SmpLoadedPackedEventConditionRowSummary,
@ -1991,6 +2022,15 @@ fn chairman_target_import_blocker(
None
}
}
RuntimeChairmanTarget::HumanChairmen | RuntimeChairmanTarget::AiChairmen => {
if company_context.known_chairman_profile_ids.is_empty() {
Some(ImportBlocker::MissingChairmanContext)
} else if !company_context.has_complete_company_controller_context {
Some(ImportBlocker::MissingCompanyRoleContext)
} else {
None
}
}
RuntimeChairmanTarget::SelectedChairman => {
if company_context.selected_chairman_profile_id.is_some() {
None
@ -1998,6 +2038,13 @@ fn chairman_target_import_blocker(
Some(ImportBlocker::MissingChairmanContext)
}
}
RuntimeChairmanTarget::ConditionTrueChairman => {
if company_context.known_chairman_profile_ids.is_empty() {
Some(ImportBlocker::MissingChairmanContext)
} else {
None
}
}
RuntimeChairmanTarget::Ids { ids } => {
if company_context.known_chairman_profile_ids.is_empty() {
Some(ImportBlocker::MissingChairmanContext)
@ -2173,6 +2220,25 @@ fn smp_runtime_effect_to_runtime_effect(
Err(company_target_import_error_message(target, company_context))
}
}
RuntimeEffect::SetCompanyGovernanceScalar {
target,
metric,
value,
} => {
if company_target_allowed_for_import(
target,
company_context,
allow_condition_true_company,
) {
Ok(RuntimeEffect::SetCompanyGovernanceScalar {
target: target.clone(),
metric: *metric,
value: *value,
})
} else {
Err(company_target_import_error_message(target, company_context))
}
}
RuntimeEffect::RetireTrains {
company_target,
territory_target,
@ -2838,10 +2904,9 @@ fn real_grouped_row_is_unsupported_chairman_target_scope(
) -> bool {
matches!(row.grouped_target_subject.as_deref(), Some("chairman"))
&& matches!(row.descriptor_id, 1 | 14)
&& row
.notes
.iter()
.any(|note| note == "chairman row requires selected-chairman scope")
&& row.notes.iter().any(|note| {
note.starts_with("chairman row uses unsupported grouped target scope ordinal ")
})
}
fn real_grouped_row_is_unsupported_territory_access_scope(
@ -2883,6 +2948,7 @@ fn real_grouped_row_is_unsupported_retire_train_scope(
fn runtime_effect_uses_condition_true_company(effect: &RuntimeEffect) -> bool {
match effect {
RuntimeEffect::SetCompanyCash { target, .. }
| RuntimeEffect::SetCompanyGovernanceScalar { target, .. }
| RuntimeEffect::SetCompanyTerritoryAccess { target, .. }
| RuntimeEffect::ConfiscateCompanyAssets { target }
| RuntimeEffect::DeactivateCompany { target }
@ -2936,12 +3002,34 @@ fn runtime_effect_uses_condition_true_player(effect: &RuntimeEffect) -> bool {
}
}
fn record_uses_condition_true_chairman(record: &SmpLoadedPackedEventRecordSummary) -> bool {
record
.decoded_actions
.iter()
.any(runtime_effect_uses_condition_true_chairman)
}
fn runtime_effect_uses_condition_true_chairman(effect: &RuntimeEffect) -> bool {
match effect {
RuntimeEffect::SetChairmanCash { target, .. }
| RuntimeEffect::DeactivateChairman { target } => {
matches!(target, RuntimeChairmanTarget::ConditionTrueChairman)
}
RuntimeEffect::AppendEventRecord { record } => record
.effects
.iter()
.any(runtime_effect_uses_condition_true_chairman),
_ => false,
}
}
fn runtime_effect_company_target_import_blocker(
effect: &RuntimeEffect,
company_context: &ImportRuntimeContext,
) -> Option<ImportBlocker> {
match effect {
RuntimeEffect::SetCompanyCash { target, .. }
| RuntimeEffect::SetCompanyGovernanceScalar { target, .. }
| RuntimeEffect::SetCompanyTerritoryAccess { target, .. }
| RuntimeEffect::ConfiscateCompanyAssets { target }
| RuntimeEffect::DeactivateCompany { target }
@ -3490,6 +3578,7 @@ mod tests {
target_mask_bits: Some(0x01),
parameter_family: Some("company_finance_scalar".to_string()),
grouped_target_subject: None,
grouped_target_scope: None,
opcode: 8,
raw_scalar_value: 7,
value_byte_0x09: 1,
@ -3520,6 +3609,7 @@ mod tests {
target_mask_bits: Some(0x01),
parameter_family: Some("company_lifecycle_toggle".to_string()),
grouped_target_subject: None,
grouped_target_scope: None,
opcode: 1,
raw_scalar_value: if enabled { 1 } else { 0 },
value_byte_0x09: 0,
@ -3551,6 +3641,7 @@ mod tests {
target_mask_bits: Some(0x01),
parameter_family: Some("company_build_limit_scalar".to_string()),
grouped_target_subject: None,
grouped_target_scope: None,
opcode: 3,
raw_scalar_value: value,
value_byte_0x09: 0,
@ -3581,6 +3672,7 @@ mod tests {
target_mask_bits: Some(0x02),
parameter_family: Some("player_lifecycle_toggle".to_string()),
grouped_target_subject: None,
grouped_target_scope: None,
opcode: 1,
raw_scalar_value: if enabled { 1 } else { 0 },
value_byte_0x09: 0,
@ -3615,6 +3707,7 @@ mod tests {
target_mask_bits: Some(0x05),
parameter_family: Some("territory_access_toggle".to_string()),
grouped_target_subject: None,
grouped_target_scope: None,
opcode: 1,
raw_scalar_value: if enabled { 1 } else { 0 },
value_byte_0x09: 0,
@ -3646,6 +3739,7 @@ mod tests {
target_mask_bits: Some(0x08),
parameter_family: Some("whole_game_state_enum".to_string()),
grouped_target_subject: None,
grouped_target_scope: None,
opcode: 3,
raw_scalar_value: value,
value_byte_0x09: 0,
@ -3676,6 +3770,7 @@ mod tests {
target_mask_bits: Some(0x08),
parameter_family: Some("world_track_build_limit_scalar".to_string()),
grouped_target_subject: None,
grouped_target_scope: None,
opcode: 3,
raw_scalar_value: value,
value_byte_0x09: 0,
@ -3706,6 +3801,7 @@ mod tests {
target_mask_bits: Some(0x08),
parameter_family: Some("special_condition_scalar".to_string()),
grouped_target_subject: None,
grouped_target_scope: None,
opcode: 3,
raw_scalar_value: value,
value_byte_0x09: 0,
@ -3736,6 +3832,7 @@ mod tests {
target_mask_bits: Some(0x08),
parameter_family: Some("candidate_availability_scalar".to_string()),
grouped_target_subject: None,
grouped_target_scope: None,
opcode: 3,
raw_scalar_value: value,
value_byte_0x09: 0,
@ -3767,6 +3864,7 @@ mod tests {
target_mask_bits: Some(0x08),
parameter_family: Some("locomotive_availability_scalar".to_string()),
grouped_target_subject: None,
grouped_target_scope: None,
opcode: 3,
raw_scalar_value: value,
value_byte_0x09: 0,
@ -3810,6 +3908,7 @@ mod tests {
target_mask_bits: Some(0x08),
parameter_family: Some("locomotive_cost_scalar".to_string()),
grouped_target_subject: None,
grouped_target_scope: None,
opcode: 3,
raw_scalar_value: value,
value_byte_0x09: 0,
@ -3998,6 +4097,7 @@ mod tests {
target_mask_bits: Some(0x08),
parameter_family: Some("cargo_production_scalar".to_string()),
grouped_target_subject: None,
grouped_target_scope: None,
opcode: 3,
raw_scalar_value: value,
value_byte_0x09: 0,
@ -4028,6 +4128,7 @@ mod tests {
target_mask_bits: Some(0x08),
parameter_family: Some("territory_access_cost_scalar".to_string()),
grouped_target_subject: None,
grouped_target_scope: None,
opcode: 3,
raw_scalar_value: value,
value_byte_0x09: 0,
@ -4060,6 +4161,7 @@ mod tests {
target_mask_bits: Some(0x08),
parameter_family: Some("world_flag_toggle".to_string()),
grouped_target_subject: None,
grouped_target_scope: None,
opcode: 0,
raw_scalar_value: if enabled { 1 } else { 0 },
value_byte_0x09: 0,
@ -4093,6 +4195,7 @@ mod tests {
target_mask_bits: Some(0x01),
parameter_family: Some("company_confiscation_variant".to_string()),
grouped_target_subject: None,
grouped_target_scope: None,
opcode: 1,
raw_scalar_value: if enabled { 1 } else { 0 },
value_byte_0x09: 0,
@ -4128,6 +4231,7 @@ mod tests {
target_mask_bits: Some(0x0d),
parameter_family: Some("company_or_territory_asset_toggle".to_string()),
grouped_target_subject: None,
grouped_target_scope: None,
opcode: 1,
raw_scalar_value: if enabled { 1 } else { 0 },
value_byte_0x09: 0,
@ -4159,6 +4263,7 @@ mod tests {
target_mask_bits: Some(0x01),
parameter_family: Some("company_confiscation_variant".to_string()),
grouped_target_subject: None,
grouped_target_scope: None,
opcode: 1,
raw_scalar_value: 0,
value_byte_0x09: 0,
@ -6005,6 +6110,7 @@ mod tests {
target_mask_bits: Some(0x08),
parameter_family: Some("locomotive_availability_scalar".to_string()),
grouped_target_subject: None,
grouped_target_scope: None,
opcode: 3,
raw_scalar_value: 42,
value_byte_0x09: 0,
@ -7155,6 +7261,7 @@ mod tests {
target_mask_bits: Some(0x01),
parameter_family: Some("company_finance_scalar".to_string()),
grouped_target_subject: None,
grouped_target_scope: None,
opcode: 8,
raw_scalar_value: 250,
value_byte_0x09: 1,
@ -8785,6 +8892,7 @@ mod tests {
target_mask_bits: Some(0x08),
parameter_family: Some("candidate_availability_scalar".to_string()),
grouped_target_subject: None,
grouped_target_scope: None,
opcode: 3,
raw_scalar_value: 1,
value_byte_0x09: 0,

View file

@ -196,7 +196,10 @@ pub enum RuntimePlayerTarget {
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum RuntimeChairmanTarget {
AllActive,
HumanChairmen,
AiChairmen,
SelectedChairman,
ConditionTrueChairman,
Ids { ids: Vec<u32> },
}
@ -402,6 +405,11 @@ pub enum RuntimeEffect {
target: RuntimeChairmanTarget,
value: i64,
},
SetCompanyGovernanceScalar {
target: RuntimeCompanyTarget,
metric: RuntimeCompanyMetric,
value: i64,
},
DeactivatePlayer {
target: RuntimePlayerTarget,
},
@ -661,6 +669,8 @@ pub struct RuntimePackedEventGroupedEffectRowSummary {
pub parameter_family: Option<String>,
#[serde(default)]
pub grouped_target_subject: Option<String>,
#[serde(default)]
pub grouped_target_scope: Option<String>,
pub opcode: u8,
pub raw_scalar_value: i32,
pub value_byte_0x09: u8,
@ -1512,6 +1522,10 @@ fn validate_runtime_effect(
| RuntimeEffect::AdjustCompanyDebt { target, .. } => {
validate_company_target(target, valid_company_ids)?;
}
RuntimeEffect::SetCompanyGovernanceScalar { target, metric, .. } => {
validate_company_target(target, valid_company_ids)?;
validate_company_governance_scalar_metric(*metric)?;
}
RuntimeEffect::SetCompanyTerritoryAccess {
target, territory, ..
} => {
@ -1761,7 +1775,11 @@ fn validate_chairman_target(
valid_chairman_profile_ids: &BTreeSet<u32>,
) -> Result<(), String> {
match target {
RuntimeChairmanTarget::AllActive | RuntimeChairmanTarget::SelectedChairman => Ok(()),
RuntimeChairmanTarget::AllActive
| RuntimeChairmanTarget::HumanChairmen
| RuntimeChairmanTarget::AiChairmen
| RuntimeChairmanTarget::SelectedChairman
| RuntimeChairmanTarget::ConditionTrueChairman => Ok(()),
RuntimeChairmanTarget::Ids { ids } => {
if ids.is_empty() {
return Err("target ids must not be empty".to_string());
@ -1778,6 +1796,19 @@ fn validate_chairman_target(
}
}
fn validate_company_governance_scalar_metric(metric: RuntimeCompanyMetric) -> Result<(), String> {
match metric {
RuntimeCompanyMetric::CreditRating
| RuntimeCompanyMetric::PrimeRate
| RuntimeCompanyMetric::BookValuePerShare
| RuntimeCompanyMetric::InvestorConfidence
| RuntimeCompanyMetric::ManagementAttitude => Ok(()),
_ => Err(
"governance scalar effect requires a writable company governance metric".to_string(),
),
}
}
fn validate_territory_target(
target: &RuntimeTerritoryTarget,
valid_territory_ids: &BTreeSet<u32>,

View file

@ -2007,6 +2007,8 @@ pub struct SmpLoadedPackedEventGroupedEffectRowSummary {
pub parameter_family: Option<String>,
#[serde(default)]
pub grouped_target_subject: Option<String>,
#[serde(default)]
pub grouped_target_scope: Option<String>,
pub opcode: u8,
pub raw_scalar_value: i32,
pub value_byte_0x09: u8,
@ -2736,9 +2738,20 @@ fn parse_real_event_runtime_record_summary(
}
if let Some(control) = compact_control.as_ref() {
for row in &mut grouped_effect_rows {
row.grouped_target_subject = derive_real_grouped_target_subject(row, control)
let target_subject = derive_real_grouped_target_subject(row, control);
let target_scope_ordinal = control
.grouped_target_scope_ordinals_0x7fb
.get(row.group_index)
.copied();
row.grouped_target_subject = target_subject
.map(real_grouped_target_subject_name)
.map(str::to_string);
row.grouped_target_scope = derive_real_grouped_target_scope_name(
row,
control,
target_subject,
target_scope_ordinal,
);
let company_target_present = control
.grouped_target_scope_ordinals_0x7fb
.get(row.group_index)
@ -2771,13 +2784,13 @@ fn parse_real_event_runtime_record_summary(
row.notes
.push("territory access row is missing company or territory scope".to_string());
}
if matches!(
derive_real_grouped_target_subject(row, control),
Some(RealGroupedTargetSubject::Chairman)
) && !chairman_target_present
if matches!(target_subject, Some(RealGroupedTargetSubject::Chairman))
&& !chairman_target_present
{
row.notes
.push("chairman row requires selected-chairman scope".to_string());
let ordinal = target_scope_ordinal.unwrap_or(u8::MAX);
row.notes.push(format!(
"chairman row uses unsupported grouped target scope ordinal {ordinal}"
));
}
}
}
@ -3337,6 +3350,7 @@ fn parse_real_grouped_effect_row_summary(
target_mask_bits: descriptor_metadata.map(|metadata| metadata.target_mask_bits),
parameter_family: descriptor_metadata.map(|metadata| metadata.parameter_family.to_string()),
grouped_target_subject: None,
grouped_target_scope: None,
opcode,
raw_scalar_value,
value_byte_0x09,
@ -3524,9 +3538,11 @@ fn real_condition_chairman_target(
RuntimePlayerConditionTestScope::SelectedPlayerOnly => {
Some(RuntimeChairmanTarget::SelectedChairman)
}
RuntimePlayerConditionTestScope::Disabled
| RuntimePlayerConditionTestScope::AiPlayersOnly
| RuntimePlayerConditionTestScope::HumanPlayersOnly => None,
RuntimePlayerConditionTestScope::AiPlayersOnly => Some(RuntimeChairmanTarget::AiChairmen),
RuntimePlayerConditionTestScope::HumanPlayersOnly => {
Some(RuntimeChairmanTarget::HumanChairmen)
}
RuntimePlayerConditionTestScope::Disabled => None,
}
}
@ -3892,6 +3908,63 @@ fn real_grouped_target_subject_name(subject: RealGroupedTargetSubject) -> &'stat
}
}
fn derive_real_grouped_target_scope_name(
row: &SmpLoadedPackedEventGroupedEffectRowSummary,
compact_control: &SmpLoadedPackedEventCompactControlSummary,
target_subject: Option<RealGroupedTargetSubject>,
target_scope_ordinal: Option<u8>,
) -> Option<String> {
match target_subject {
Some(RealGroupedTargetSubject::Company) => target_scope_ordinal
.map(real_grouped_company_scope_name)
.map(str::to_string),
Some(RealGroupedTargetSubject::Player) => target_scope_ordinal
.map(real_grouped_player_scope_name)
.map(str::to_string),
Some(RealGroupedTargetSubject::Chairman) => target_scope_ordinal
.map(real_grouped_chairman_scope_name)
.map(str::to_string),
Some(RealGroupedTargetSubject::Territory) => compact_control
.grouped_territory_selectors_0x80f
.get(row.group_index)
.copied()
.filter(|selector| *selector >= 0)
.map(|_| "specified_territories".to_string()),
Some(RealGroupedTargetSubject::WholeGame) => Some("whole_game".to_string()),
None => None,
}
}
fn real_grouped_company_scope_name(ordinal: u8) -> &'static str {
match ordinal {
0 => "condition_true_company",
1 => "selected_company",
2 => "human_companies",
3 => "ai_companies",
_ => "unsupported_company_scope",
}
}
fn real_grouped_player_scope_name(ordinal: u8) -> &'static str {
match ordinal {
0 => "condition_true_player",
1 => "selected_player",
2 => "human_players",
3 => "ai_players",
_ => "unsupported_player_scope",
}
}
fn real_grouped_chairman_scope_name(ordinal: u8) -> &'static str {
match ordinal {
0 => "condition_true_chairman",
1 => "selected_chairman",
2 => "human_chairmen",
3 => "ai_chairmen",
_ => "unsupported_chairman_scope",
}
}
fn decode_real_grouped_effect_actions(
grouped_effect_rows: &[SmpLoadedPackedEventGroupedEffectRowSummary],
compact_control: &SmpLoadedPackedEventCompactControlSummary,
@ -4127,7 +4200,10 @@ fn real_grouped_player_target(ordinal: u8) -> Option<RuntimePlayerTarget> {
fn real_grouped_chairman_target(ordinal: u8) -> Option<RuntimeChairmanTarget> {
match ordinal {
0 => Some(RuntimeChairmanTarget::ConditionTrueChairman),
1 => Some(RuntimeChairmanTarget::SelectedChairman),
2 => Some(RuntimeChairmanTarget::HumanChairmen),
3 => Some(RuntimeChairmanTarget::AiChairmen),
_ => None,
}
}
@ -4287,12 +4363,16 @@ fn runtime_effect_supported_for_save_import(effect: &RuntimeEffect) -> bool {
| RuntimeEffect::DeactivateChairman { target } => matches!(
target,
RuntimeChairmanTarget::AllActive
| RuntimeChairmanTarget::HumanChairmen
| RuntimeChairmanTarget::AiChairmen
| RuntimeChairmanTarget::SelectedChairman
| RuntimeChairmanTarget::ConditionTrueChairman
| RuntimeChairmanTarget::Ids { .. }
),
RuntimeEffect::SetWorldFlag { .. }
| RuntimeEffect::SetLimitedTrackBuildingAmount { .. }
| RuntimeEffect::SetEconomicStatusCode { .. }
| RuntimeEffect::SetCompanyGovernanceScalar { .. }
| RuntimeEffect::SetCandidateAvailability { .. }
| RuntimeEffect::SetNamedLocomotiveAvailability { .. }
| RuntimeEffect::SetNamedLocomotiveAvailabilityValue { .. }

View file

@ -87,7 +87,6 @@ struct AppliedEffectsSummary {
struct ResolvedConditionContext {
matching_company_ids: BTreeSet<u32>,
matching_player_ids: BTreeSet<u32>,
#[allow(dead_code)]
matching_chairman_profile_ids: BTreeSet<u32>,
}
@ -363,6 +362,48 @@ fn apply_runtime_effects(
chairman.current_cash = *value;
}
}
RuntimeEffect::SetCompanyGovernanceScalar {
target,
metric,
value,
} => {
let company_ids = resolve_company_target_ids(state, target, condition_context)?;
for company_id in company_ids {
let company = state
.companies
.iter_mut()
.find(|company| company.company_id == company_id)
.ok_or_else(|| {
format!(
"missing company_id {company_id} while applying governance effect"
)
})?;
match metric {
RuntimeCompanyMetric::CreditRating => {
company.credit_rating_score = Some(*value);
}
RuntimeCompanyMetric::PrimeRate => {
company.prime_rate = Some(*value);
}
RuntimeCompanyMetric::BookValuePerShare => {
company.book_value_per_share = *value;
}
RuntimeCompanyMetric::InvestorConfidence => {
company.investor_confidence = *value;
}
RuntimeCompanyMetric::ManagementAttitude => {
company.management_attitude = *value;
}
_ => {
return Err(format!(
"unsupported governance metric {:?} in company governance effect",
metric
));
}
}
mutated_company_ids.insert(company_id);
}
}
RuntimeEffect::DeactivatePlayer { target } => {
let player_ids = resolve_player_target_ids(state, target, condition_context)?;
for player_id in player_ids {
@ -1152,7 +1193,7 @@ fn resolve_player_target_ids(
fn resolve_chairman_target_ids(
state: &RuntimeState,
target: &RuntimeChairmanTarget,
_condition_context: &ResolvedConditionContext,
condition_context: &ResolvedConditionContext,
) -> Result<Vec<u32>, String> {
match target {
RuntimeChairmanTarget::AllActive => Ok(state
@ -1161,6 +1202,30 @@ fn resolve_chairman_target_ids(
.filter(|profile| profile.active)
.map(|profile| profile.profile_id)
.collect()),
RuntimeChairmanTarget::HumanChairmen => Ok(state
.chairman_profiles
.iter()
.filter(|profile| {
chairman_profile_matches_company_controller_kind(
state,
profile,
RuntimeCompanyControllerKind::Human,
)
})
.map(|profile| profile.profile_id)
.collect()),
RuntimeChairmanTarget::AiChairmen => Ok(state
.chairman_profiles
.iter()
.filter(|profile| {
chairman_profile_matches_company_controller_kind(
state,
profile,
RuntimeCompanyControllerKind::Ai,
)
})
.map(|profile| profile.profile_id)
.collect()),
RuntimeChairmanTarget::Ids { ids } => {
let known_ids = state
.chairman_profiles
@ -1193,9 +1258,37 @@ fn resolve_chairman_target_ids(
)
}
}
RuntimeChairmanTarget::ConditionTrueChairman => {
if condition_context.matching_chairman_profile_ids.is_empty() {
Err("target requires chairman condition-evaluation context".to_string())
} else {
Ok(condition_context
.matching_chairman_profile_ids
.iter()
.copied()
.collect())
}
}
}
}
fn chairman_profile_matches_company_controller_kind(
state: &RuntimeState,
profile: &crate::RuntimeChairmanProfile,
controller_kind: RuntimeCompanyControllerKind,
) -> bool {
profile.active
&& profile
.linked_company_id
.and_then(|company_id| {
state
.companies
.iter()
.find(|company| company.company_id == company_id)
})
.is_some_and(|company| company.controller_kind == controller_kind)
}
fn resolve_territory_target_ids(
state: &RuntimeState,
target: &RuntimeTerritoryTarget,