Implement ordinary packed event conditions
This commit is contained in:
parent
087ebf1097
commit
f73234cb99
28 changed files with 2624 additions and 86 deletions
10
README.md
10
README.md
|
|
@ -17,10 +17,12 @@ selected-company and controller-role context through overlay imports, and real d
|
||||||
`Company Cash`, `13` `Deactivate Company`, and `16` `Company Track Pieces Buildable` now parse and
|
`Company Cash`, `13` `Deactivate Company`, and `16` `Company Track Pieces Buildable` now parse and
|
||||||
execute through the ordinary runtime path. Synthetic packed records still exercise the same service
|
execute through the ordinary runtime path. Synthetic packed records still exercise the same service
|
||||||
engine without a parallel packed executor. The first grounded condition-side unlock now exists for
|
engine without a parallel packed executor. The first grounded condition-side unlock now exists for
|
||||||
negative-sentinel `raw_condition_id = -1` company scopes, while ordinary condition-id semantics and
|
negative-sentinel `raw_condition_id = -1` company scopes, and the first ordinary nonnegative
|
||||||
player/territory runtime ownership remain blocked. Mixed supported/unsupported real rows still stay
|
condition batch now executes too: numeric-threshold company finance, company track, aggregate
|
||||||
parity-only. The PE32 hook remains useful as capture and integration tooling, but it is no longer
|
territory track, and company-territory track rows can import through overlay-backed runtime
|
||||||
the main execution milestone.
|
context. Named-territory bindings and player-owned condition scope still remain blocked. Mixed
|
||||||
|
supported/unsupported real rows still stay parity-only. The PE32 hook remains useful as capture and
|
||||||
|
integration tooling, but it is no longer the main execution milestone.
|
||||||
|
|
||||||
## Project Docs
|
## Project Docs
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,7 @@ mod tests {
|
||||||
CalendarPoint, OVERLAY_IMPORT_DOCUMENT_FORMAT_VERSION, RuntimeOverlayImportDocument,
|
CalendarPoint, OVERLAY_IMPORT_DOCUMENT_FORMAT_VERSION, RuntimeOverlayImportDocument,
|
||||||
RuntimeOverlayImportDocumentSource, RuntimeSaveProfileState, RuntimeSaveSliceDocument,
|
RuntimeOverlayImportDocumentSource, RuntimeSaveProfileState, RuntimeSaveSliceDocument,
|
||||||
RuntimeSaveSliceDocumentSource, RuntimeServiceState, RuntimeSnapshotDocument,
|
RuntimeSaveSliceDocumentSource, RuntimeServiceState, RuntimeSnapshotDocument,
|
||||||
RuntimeSnapshotSource, RuntimeState, RuntimeWorldRestoreState,
|
RuntimeSnapshotSource, RuntimeState, RuntimeTrackPieceCounts, RuntimeWorldRestoreState,
|
||||||
SAVE_SLICE_DOCUMENT_FORMAT_VERSION, SNAPSHOT_FORMAT_VERSION,
|
SAVE_SLICE_DOCUMENT_FORMAT_VERSION, SNAPSHOT_FORMAT_VERSION,
|
||||||
save_runtime_overlay_import_document, save_runtime_save_slice_document,
|
save_runtime_overlay_import_document, save_runtime_save_slice_document,
|
||||||
save_runtime_snapshot_document,
|
save_runtime_snapshot_document,
|
||||||
|
|
@ -174,6 +174,8 @@ mod tests {
|
||||||
metadata: BTreeMap::new(),
|
metadata: BTreeMap::new(),
|
||||||
companies: Vec::new(),
|
companies: Vec::new(),
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
|
territories: Vec::new(),
|
||||||
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
@ -330,10 +332,15 @@ mod tests {
|
||||||
controller_kind: rrt_runtime::RuntimeCompanyControllerKind::Human,
|
controller_kind: rrt_runtime::RuntimeCompanyControllerKind::Human,
|
||||||
current_cash: 100,
|
current_cash: 100,
|
||||||
debt: 0,
|
debt: 0,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
active: true,
|
active: true,
|
||||||
available_track_laying_capacity: None,
|
available_track_laying_capacity: None,
|
||||||
}],
|
}],
|
||||||
selected_company_id: Some(42),
|
selected_company_id: Some(42),
|
||||||
|
territories: Vec::new(),
|
||||||
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
@ -391,6 +398,7 @@ mod tests {
|
||||||
negative_sentinel_scope: None,
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||||
grouped_effect_rows: vec![],
|
grouped_effect_rows: vec![],
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
decoded_actions: vec![rrt_runtime::RuntimeEffect::AdjustCompanyCash {
|
decoded_actions: vec![rrt_runtime::RuntimeEffect::AdjustCompanyCash {
|
||||||
target: rrt_runtime::RuntimeCompanyTarget::Ids { ids: vec![42] },
|
target: rrt_runtime::RuntimeCompanyTarget::Ids { ids: vec![42] },
|
||||||
delta: 25,
|
delta: 25,
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,10 @@ pub struct ExpectedRuntimeSummary {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub active_company_count: Option<usize>,
|
pub active_company_count: Option<usize>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub territory_count: Option<usize>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub company_territory_track_count: Option<usize>,
|
||||||
|
#[serde(default)]
|
||||||
pub packed_event_collection_present: Option<bool>,
|
pub packed_event_collection_present: Option<bool>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub packed_event_record_count: Option<usize>,
|
pub packed_event_record_count: Option<usize>,
|
||||||
|
|
@ -90,6 +94,12 @@ pub struct ExpectedRuntimeSummary {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub packed_event_blocked_territory_condition_scope_count: Option<usize>,
|
pub packed_event_blocked_territory_condition_scope_count: Option<usize>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub packed_event_blocked_missing_territory_context_count: Option<usize>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub packed_event_blocked_named_territory_binding_count: Option<usize>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub packed_event_blocked_unmapped_ordinary_condition_count: Option<usize>,
|
||||||
|
#[serde(default)]
|
||||||
pub packed_event_blocked_missing_compact_control_count: Option<usize>,
|
pub packed_event_blocked_missing_compact_control_count: Option<usize>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub packed_event_blocked_unmapped_real_descriptor_count: Option<usize>,
|
pub packed_event_blocked_unmapped_real_descriptor_count: Option<usize>,
|
||||||
|
|
@ -343,6 +353,22 @@ impl ExpectedRuntimeSummary {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(count) = self.territory_count {
|
||||||
|
if actual.territory_count != count {
|
||||||
|
mismatches.push(format!(
|
||||||
|
"territory_count mismatch: expected {count}, got {}",
|
||||||
|
actual.territory_count
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(count) = self.company_territory_track_count {
|
||||||
|
if actual.company_territory_track_count != count {
|
||||||
|
mismatches.push(format!(
|
||||||
|
"company_territory_track_count mismatch: expected {count}, got {}",
|
||||||
|
actual.company_territory_track_count
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Some(present) = self.packed_event_collection_present {
|
if let Some(present) = self.packed_event_collection_present {
|
||||||
if actual.packed_event_collection_present != present {
|
if actual.packed_event_collection_present != present {
|
||||||
mismatches.push(format!(
|
mismatches.push(format!(
|
||||||
|
|
@ -447,6 +473,30 @@ impl ExpectedRuntimeSummary {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(count) = self.packed_event_blocked_missing_territory_context_count {
|
||||||
|
if actual.packed_event_blocked_missing_territory_context_count != count {
|
||||||
|
mismatches.push(format!(
|
||||||
|
"packed_event_blocked_missing_territory_context_count mismatch: expected {count}, got {}",
|
||||||
|
actual.packed_event_blocked_missing_territory_context_count
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(count) = self.packed_event_blocked_named_territory_binding_count {
|
||||||
|
if actual.packed_event_blocked_named_territory_binding_count != count {
|
||||||
|
mismatches.push(format!(
|
||||||
|
"packed_event_blocked_named_territory_binding_count mismatch: expected {count}, got {}",
|
||||||
|
actual.packed_event_blocked_named_territory_binding_count
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(count) = self.packed_event_blocked_unmapped_ordinary_condition_count {
|
||||||
|
if actual.packed_event_blocked_unmapped_ordinary_condition_count != count {
|
||||||
|
mismatches.push(format!(
|
||||||
|
"packed_event_blocked_unmapped_ordinary_condition_count mismatch: expected {count}, got {}",
|
||||||
|
actual.packed_event_blocked_unmapped_ordinary_condition_count
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Some(count) = self.packed_event_blocked_missing_compact_control_count {
|
if let Some(count) = self.packed_event_blocked_missing_compact_control_count {
|
||||||
if actual.packed_event_blocked_missing_compact_control_count != count {
|
if actual.packed_event_blocked_missing_compact_control_count != count {
|
||||||
mismatches.push(format!(
|
mismatches.push(format!(
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,13 @@ use serde::{Deserialize, Serialize};
|
||||||
use crate::persistence::{load_runtime_snapshot_document, validate_runtime_snapshot_document};
|
use crate::persistence::{load_runtime_snapshot_document, validate_runtime_snapshot_document};
|
||||||
use crate::{
|
use crate::{
|
||||||
CalendarPoint, RuntimeCompanyConditionTestScope, RuntimeCompanyControllerKind,
|
CalendarPoint, RuntimeCompanyConditionTestScope, RuntimeCompanyControllerKind,
|
||||||
RuntimeCompanyTarget, RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate,
|
RuntimeCompanyTarget, RuntimeCondition, RuntimeEffect, RuntimeEventRecord,
|
||||||
RuntimePackedEventCollectionSummary, RuntimePackedEventCompactControlSummary,
|
RuntimeEventRecordTemplate, RuntimePackedEventCollectionSummary,
|
||||||
RuntimePackedEventConditionRowSummary, RuntimePackedEventGroupedEffectRowSummary,
|
RuntimePackedEventCompactControlSummary, RuntimePackedEventConditionRowSummary,
|
||||||
RuntimePackedEventNegativeSentinelScopeSummary, RuntimePackedEventRecordSummary,
|
RuntimePackedEventGroupedEffectRowSummary, RuntimePackedEventNegativeSentinelScopeSummary,
|
||||||
RuntimePackedEventTextBandSummary, RuntimePlayerConditionTestScope, RuntimeSaveProfileState,
|
RuntimePackedEventRecordSummary, RuntimePackedEventTextBandSummary,
|
||||||
RuntimeServiceState, RuntimeState, RuntimeWorldRestoreState,
|
RuntimePlayerConditionTestScope, RuntimeSaveProfileState, RuntimeServiceState, RuntimeState,
|
||||||
|
RuntimeWorldRestoreState,
|
||||||
SmpLoadedPackedEventNegativeSentinelScopeSummary, SmpLoadedPackedEventRecordSummary,
|
SmpLoadedPackedEventNegativeSentinelScopeSummary, SmpLoadedPackedEventRecordSummary,
|
||||||
SmpLoadedPackedEventTextBandSummary, SmpLoadedSaveSlice,
|
SmpLoadedPackedEventTextBandSummary, SmpLoadedSaveSlice,
|
||||||
};
|
};
|
||||||
|
|
@ -106,6 +107,7 @@ struct ImportCompanyContext {
|
||||||
known_company_ids: BTreeSet<u32>,
|
known_company_ids: BTreeSet<u32>,
|
||||||
selected_company_id: Option<u32>,
|
selected_company_id: Option<u32>,
|
||||||
has_complete_controller_context: bool,
|
has_complete_controller_context: bool,
|
||||||
|
has_territory_context: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
|
@ -117,6 +119,9 @@ enum CompanyTargetImportBlocker {
|
||||||
CompanyConditionScopeDisabled,
|
CompanyConditionScopeDisabled,
|
||||||
PlayerConditionScope,
|
PlayerConditionScope,
|
||||||
TerritoryConditionScope,
|
TerritoryConditionScope,
|
||||||
|
MissingTerritoryContext,
|
||||||
|
NamedTerritoryBinding,
|
||||||
|
UnmappedOrdinaryCondition,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ImportCompanyContext {
|
impl ImportCompanyContext {
|
||||||
|
|
@ -125,6 +130,7 @@ impl ImportCompanyContext {
|
||||||
known_company_ids: BTreeSet::new(),
|
known_company_ids: BTreeSet::new(),
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
has_complete_controller_context: false,
|
has_complete_controller_context: false,
|
||||||
|
has_territory_context: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -140,6 +146,7 @@ impl ImportCompanyContext {
|
||||||
&& state.companies.iter().all(|company| {
|
&& state.companies.iter().all(|company| {
|
||||||
company.controller_kind != RuntimeCompanyControllerKind::Unknown
|
company.controller_kind != RuntimeCompanyControllerKind::Unknown
|
||||||
}),
|
}),
|
||||||
|
has_territory_context: !state.territories.is_empty(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -171,6 +178,8 @@ pub fn project_save_slice_to_runtime_state_import(
|
||||||
metadata: projection.metadata,
|
metadata: projection.metadata,
|
||||||
companies: Vec::new(),
|
companies: Vec::new(),
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
|
territories: Vec::new(),
|
||||||
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
packed_event_collection: projection.packed_event_collection,
|
packed_event_collection: projection.packed_event_collection,
|
||||||
event_runtime_records: projection.event_runtime_records,
|
event_runtime_records: projection.event_runtime_records,
|
||||||
candidate_availability: projection.candidate_availability,
|
candidate_availability: projection.candidate_availability,
|
||||||
|
|
@ -220,6 +229,10 @@ pub fn project_save_slice_overlay_to_runtime_state_import(
|
||||||
metadata,
|
metadata,
|
||||||
companies: base_state.companies.clone(),
|
companies: base_state.companies.clone(),
|
||||||
selected_company_id: base_state.selected_company_id,
|
selected_company_id: base_state.selected_company_id,
|
||||||
|
territories: base_state.territories.clone(),
|
||||||
|
company_territory_track_piece_counts: base_state
|
||||||
|
.company_territory_track_piece_counts
|
||||||
|
.clone(),
|
||||||
packed_event_collection: projection.packed_event_collection,
|
packed_event_collection: projection.packed_event_collection,
|
||||||
event_runtime_records: projection.event_runtime_records,
|
event_runtime_records: projection.event_runtime_records,
|
||||||
candidate_availability: projection.candidate_availability,
|
candidate_availability: projection.candidate_availability,
|
||||||
|
|
@ -597,8 +610,10 @@ fn runtime_packed_event_record_summary_from_smp(
|
||||||
company_context: &ImportCompanyContext,
|
company_context: &ImportCompanyContext,
|
||||||
imported: bool,
|
imported: bool,
|
||||||
) -> RuntimePackedEventRecordSummary {
|
) -> RuntimePackedEventRecordSummary {
|
||||||
let lowered_decoded_actions =
|
let lowered_decoded_conditions = lowered_record_decoded_conditions(record, company_context)
|
||||||
lowered_record_decoded_actions(record).unwrap_or_else(|_| record.decoded_actions.clone());
|
.unwrap_or_else(|_| record.decoded_conditions.clone());
|
||||||
|
let lowered_decoded_actions = lowered_record_decoded_actions(record, company_context)
|
||||||
|
.unwrap_or_else(|_| record.decoded_actions.clone());
|
||||||
RuntimePackedEventRecordSummary {
|
RuntimePackedEventRecordSummary {
|
||||||
record_index: record.record_index,
|
record_index: record.record_index,
|
||||||
live_entry_id: record.live_entry_id,
|
live_entry_id: record.live_entry_id,
|
||||||
|
|
@ -636,6 +651,7 @@ fn runtime_packed_event_record_summary_from_smp(
|
||||||
.map(runtime_packed_event_grouped_effect_row_summary_from_smp)
|
.map(runtime_packed_event_grouped_effect_row_summary_from_smp)
|
||||||
.collect(),
|
.collect(),
|
||||||
grouped_company_targets: classify_real_grouped_company_targets(record),
|
grouped_company_targets: classify_real_grouped_company_targets(record),
|
||||||
|
decoded_conditions: lowered_decoded_conditions,
|
||||||
decoded_actions: lowered_decoded_actions,
|
decoded_actions: lowered_decoded_actions,
|
||||||
executable_import_ready: record.executable_import_ready,
|
executable_import_ready: record.executable_import_ready,
|
||||||
import_outcome: Some(determine_packed_event_import_outcome(
|
import_outcome: Some(determine_packed_event_import_outcome(
|
||||||
|
|
@ -695,6 +711,11 @@ fn runtime_packed_event_condition_row_summary_from_smp(
|
||||||
subtype: row.subtype,
|
subtype: row.subtype,
|
||||||
flag_bytes: row.flag_bytes.clone(),
|
flag_bytes: row.flag_bytes.clone(),
|
||||||
candidate_name: row.candidate_name.clone(),
|
candidate_name: row.candidate_name.clone(),
|
||||||
|
comparator: row.comparator.clone(),
|
||||||
|
metric: row.metric.clone(),
|
||||||
|
semantic_family: row.semantic_family.clone(),
|
||||||
|
semantic_preview: row.semantic_preview.clone(),
|
||||||
|
requires_candidate_name_binding: row.requires_candidate_name_binding,
|
||||||
notes: row.notes.clone(),
|
notes: row.notes.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -738,11 +759,19 @@ fn smp_packed_record_to_runtime_event_record(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let lowered_effects = match lowered_record_decoded_actions(record) {
|
let lowered_conditions = match lowered_record_decoded_conditions(record, company_context) {
|
||||||
|
Ok(conditions) => conditions,
|
||||||
|
Err(_) => return None,
|
||||||
|
};
|
||||||
|
let lowered_effects = match lowered_record_decoded_actions(record, company_context) {
|
||||||
Ok(effects) => effects,
|
Ok(effects) => effects,
|
||||||
Err(_) => return None,
|
Err(_) => return None,
|
||||||
};
|
};
|
||||||
let effects = match smp_runtime_effects_to_runtime_effects(&lowered_effects, company_context) {
|
let effects = match smp_runtime_effects_to_runtime_effects(
|
||||||
|
&lowered_effects,
|
||||||
|
company_context,
|
||||||
|
conditions_provide_company_context(&lowered_conditions),
|
||||||
|
) {
|
||||||
Ok(effects) => effects,
|
Ok(effects) => effects,
|
||||||
Err(_) => return None,
|
Err(_) => return None,
|
||||||
};
|
};
|
||||||
|
|
@ -763,19 +792,42 @@ fn smp_packed_record_to_runtime_event_record(
|
||||||
active,
|
active,
|
||||||
marks_collection_dirty,
|
marks_collection_dirty,
|
||||||
one_shot,
|
one_shot,
|
||||||
|
conditions: lowered_conditions,
|
||||||
effects,
|
effects,
|
||||||
}
|
}
|
||||||
.into_runtime_record())
|
.into_runtime_record())
|
||||||
})())
|
})())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lowered_record_decoded_actions(
|
fn lowered_record_decoded_conditions(
|
||||||
record: &SmpLoadedPackedEventRecordSummary,
|
record: &SmpLoadedPackedEventRecordSummary,
|
||||||
) -> Result<Vec<RuntimeEffect>, CompanyTargetImportBlocker> {
|
company_context: &ImportCompanyContext,
|
||||||
if let Some(blocker) = packed_record_condition_scope_import_blocker(record) {
|
) -> Result<Vec<RuntimeCondition>, CompanyTargetImportBlocker> {
|
||||||
|
if let Some(blocker) = packed_record_condition_scope_import_blocker(record, company_context) {
|
||||||
return Err(blocker);
|
return Err(blocker);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let Some(lowered_target) = lowered_condition_true_company_target(record) else {
|
||||||
|
return Ok(record.decoded_conditions.clone());
|
||||||
|
};
|
||||||
|
Ok(record
|
||||||
|
.decoded_conditions
|
||||||
|
.iter()
|
||||||
|
.map(|condition| lower_condition_true_company_target_in_condition(condition, &lowered_target))
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lowered_record_decoded_actions(
|
||||||
|
record: &SmpLoadedPackedEventRecordSummary,
|
||||||
|
company_context: &ImportCompanyContext,
|
||||||
|
) -> Result<Vec<RuntimeEffect>, CompanyTargetImportBlocker> {
|
||||||
|
if let Some(blocker) = packed_record_condition_scope_import_blocker(record, company_context) {
|
||||||
|
return Err(blocker);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !record.decoded_conditions.is_empty() {
|
||||||
|
return Ok(record.decoded_actions.clone());
|
||||||
|
}
|
||||||
let Some(lowered_target) = lowered_condition_true_company_target(record) else {
|
let Some(lowered_target) = lowered_condition_true_company_target(record) else {
|
||||||
return Ok(record.decoded_actions.clone());
|
return Ok(record.decoded_actions.clone());
|
||||||
};
|
};
|
||||||
|
|
@ -788,20 +840,53 @@ fn lowered_record_decoded_actions(
|
||||||
|
|
||||||
fn packed_record_condition_scope_import_blocker(
|
fn packed_record_condition_scope_import_blocker(
|
||||||
record: &SmpLoadedPackedEventRecordSummary,
|
record: &SmpLoadedPackedEventRecordSummary,
|
||||||
|
company_context: &ImportCompanyContext,
|
||||||
) -> Option<CompanyTargetImportBlocker> {
|
) -> Option<CompanyTargetImportBlocker> {
|
||||||
if record.standalone_condition_rows.is_empty() {
|
if record.standalone_condition_rows.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let ordinary_condition_row_count = record
|
||||||
|
.standalone_condition_rows
|
||||||
|
.iter()
|
||||||
|
.filter(|row| row.raw_condition_id >= 0)
|
||||||
|
.count();
|
||||||
|
if ordinary_condition_row_count != 0 {
|
||||||
|
if record
|
||||||
|
.standalone_condition_rows
|
||||||
|
.iter()
|
||||||
|
.any(|row| row.requires_candidate_name_binding)
|
||||||
|
{
|
||||||
|
return Some(CompanyTargetImportBlocker::NamedTerritoryBinding);
|
||||||
|
}
|
||||||
|
if ordinary_condition_row_count != record.decoded_conditions.len() {
|
||||||
|
return Some(CompanyTargetImportBlocker::UnmappedOrdinaryCondition);
|
||||||
|
}
|
||||||
|
if record
|
||||||
|
.decoded_conditions
|
||||||
|
.iter()
|
||||||
|
.any(|condition| matches!(condition, RuntimeCondition::TerritoryNumericThreshold { .. } | RuntimeCondition::CompanyTerritoryNumericThreshold { .. }))
|
||||||
|
&& !company_context.has_territory_context
|
||||||
|
{
|
||||||
|
return Some(CompanyTargetImportBlocker::MissingTerritoryContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let negative_sentinel_row_count = record
|
let negative_sentinel_row_count = record
|
||||||
.standalone_condition_rows
|
.standalone_condition_rows
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|row| row.raw_condition_id == -1)
|
.filter(|row| row.raw_condition_id == -1)
|
||||||
.count();
|
.count();
|
||||||
if negative_sentinel_row_count == 0 {
|
if negative_sentinel_row_count == 0 {
|
||||||
return Some(CompanyTargetImportBlocker::MissingConditionContext);
|
return if ordinary_condition_row_count == 0 {
|
||||||
|
Some(CompanyTargetImportBlocker::MissingConditionContext)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
}
|
}
|
||||||
if negative_sentinel_row_count != record.standalone_condition_rows.len() {
|
if ordinary_condition_row_count == 0
|
||||||
|
&& negative_sentinel_row_count != record.standalone_condition_rows.len()
|
||||||
|
{
|
||||||
return Some(CompanyTargetImportBlocker::MissingConditionContext);
|
return Some(CompanyTargetImportBlocker::MissingConditionContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -811,10 +896,22 @@ fn packed_record_condition_scope_import_blocker(
|
||||||
if scope.player_test_scope != RuntimePlayerConditionTestScope::Disabled {
|
if scope.player_test_scope != RuntimePlayerConditionTestScope::Disabled {
|
||||||
return Some(CompanyTargetImportBlocker::PlayerConditionScope);
|
return Some(CompanyTargetImportBlocker::PlayerConditionScope);
|
||||||
}
|
}
|
||||||
if scope.territory_scope_selector_is_0x63 {
|
if ordinary_condition_row_count == 0 && scope.territory_scope_selector_is_0x63 {
|
||||||
return Some(CompanyTargetImportBlocker::TerritoryConditionScope);
|
return Some(CompanyTargetImportBlocker::TerritoryConditionScope);
|
||||||
}
|
}
|
||||||
if scope.company_test_scope == RuntimeCompanyConditionTestScope::Disabled {
|
if record.decoded_conditions.iter().any(|condition| {
|
||||||
|
matches!(
|
||||||
|
condition,
|
||||||
|
RuntimeCondition::CompanyNumericThreshold { .. }
|
||||||
|
| RuntimeCondition::CompanyTerritoryNumericThreshold { .. }
|
||||||
|
)
|
||||||
|
}) && scope.company_test_scope == RuntimeCompanyConditionTestScope::Disabled
|
||||||
|
{
|
||||||
|
return Some(CompanyTargetImportBlocker::CompanyConditionScopeDisabled);
|
||||||
|
}
|
||||||
|
if ordinary_condition_row_count == 0
|
||||||
|
&& scope.company_test_scope == RuntimeCompanyConditionTestScope::Disabled
|
||||||
|
{
|
||||||
return Some(CompanyTargetImportBlocker::CompanyConditionScopeDisabled);
|
return Some(CompanyTargetImportBlocker::CompanyConditionScopeDisabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -890,6 +987,7 @@ fn lower_condition_true_company_target_in_effect(
|
||||||
active: record.active,
|
active: record.active,
|
||||||
marks_collection_dirty: record.marks_collection_dirty,
|
marks_collection_dirty: record.marks_collection_dirty,
|
||||||
one_shot: record.one_shot,
|
one_shot: record.one_shot,
|
||||||
|
conditions: record.conditions.clone(),
|
||||||
effects: record
|
effects: record
|
||||||
.effects
|
.effects
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -913,6 +1011,45 @@ fn lower_condition_true_company_target_in_effect(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn lower_condition_true_company_target_in_condition(
|
||||||
|
condition: &RuntimeCondition,
|
||||||
|
lowered_target: &RuntimeCompanyTarget,
|
||||||
|
) -> RuntimeCondition {
|
||||||
|
match condition {
|
||||||
|
RuntimeCondition::CompanyNumericThreshold {
|
||||||
|
target,
|
||||||
|
metric,
|
||||||
|
comparator,
|
||||||
|
value,
|
||||||
|
} => RuntimeCondition::CompanyNumericThreshold {
|
||||||
|
target: lower_condition_true_company_target_in_company_target(target, lowered_target),
|
||||||
|
metric: *metric,
|
||||||
|
comparator: *comparator,
|
||||||
|
value: *value,
|
||||||
|
},
|
||||||
|
RuntimeCondition::TerritoryNumericThreshold {
|
||||||
|
metric,
|
||||||
|
comparator,
|
||||||
|
value,
|
||||||
|
} => RuntimeCondition::TerritoryNumericThreshold {
|
||||||
|
metric: *metric,
|
||||||
|
comparator: *comparator,
|
||||||
|
value: *value,
|
||||||
|
},
|
||||||
|
RuntimeCondition::CompanyTerritoryNumericThreshold {
|
||||||
|
target,
|
||||||
|
metric,
|
||||||
|
comparator,
|
||||||
|
value,
|
||||||
|
} => RuntimeCondition::CompanyTerritoryNumericThreshold {
|
||||||
|
target: lower_condition_true_company_target_in_company_target(target, lowered_target),
|
||||||
|
metric: *metric,
|
||||||
|
comparator: *comparator,
|
||||||
|
value: *value,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn lower_condition_true_company_target_in_company_target(
|
fn lower_condition_true_company_target_in_company_target(
|
||||||
target: &RuntimeCompanyTarget,
|
target: &RuntimeCompanyTarget,
|
||||||
lowered_target: &RuntimeCompanyTarget,
|
lowered_target: &RuntimeCompanyTarget,
|
||||||
|
|
@ -926,16 +1063,24 @@ fn lower_condition_true_company_target_in_company_target(
|
||||||
fn smp_runtime_effects_to_runtime_effects(
|
fn smp_runtime_effects_to_runtime_effects(
|
||||||
effects: &[RuntimeEffect],
|
effects: &[RuntimeEffect],
|
||||||
company_context: &ImportCompanyContext,
|
company_context: &ImportCompanyContext,
|
||||||
|
allow_condition_true_company: bool,
|
||||||
) -> Result<Vec<RuntimeEffect>, String> {
|
) -> Result<Vec<RuntimeEffect>, String> {
|
||||||
effects
|
effects
|
||||||
.iter()
|
.iter()
|
||||||
.map(|effect| smp_runtime_effect_to_runtime_effect(effect, company_context))
|
.map(|effect| {
|
||||||
|
smp_runtime_effect_to_runtime_effect(
|
||||||
|
effect,
|
||||||
|
company_context,
|
||||||
|
allow_condition_true_company,
|
||||||
|
)
|
||||||
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn smp_runtime_effect_to_runtime_effect(
|
fn smp_runtime_effect_to_runtime_effect(
|
||||||
effect: &RuntimeEffect,
|
effect: &RuntimeEffect,
|
||||||
company_context: &ImportCompanyContext,
|
company_context: &ImportCompanyContext,
|
||||||
|
allow_condition_true_company: bool,
|
||||||
) -> Result<RuntimeEffect, String> {
|
) -> Result<RuntimeEffect, String> {
|
||||||
match effect {
|
match effect {
|
||||||
RuntimeEffect::SetWorldFlag { key, value } => Ok(RuntimeEffect::SetWorldFlag {
|
RuntimeEffect::SetWorldFlag { key, value } => Ok(RuntimeEffect::SetWorldFlag {
|
||||||
|
|
@ -943,7 +1088,11 @@ fn smp_runtime_effect_to_runtime_effect(
|
||||||
value: *value,
|
value: *value,
|
||||||
}),
|
}),
|
||||||
RuntimeEffect::SetCompanyCash { target, value } => {
|
RuntimeEffect::SetCompanyCash { target, value } => {
|
||||||
if company_target_import_blocker(target, company_context).is_none() {
|
if company_target_allowed_for_import(
|
||||||
|
target,
|
||||||
|
company_context,
|
||||||
|
allow_condition_true_company,
|
||||||
|
) {
|
||||||
Ok(RuntimeEffect::SetCompanyCash {
|
Ok(RuntimeEffect::SetCompanyCash {
|
||||||
target: target.clone(),
|
target: target.clone(),
|
||||||
value: *value,
|
value: *value,
|
||||||
|
|
@ -953,7 +1102,11 @@ fn smp_runtime_effect_to_runtime_effect(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RuntimeEffect::DeactivateCompany { target } => {
|
RuntimeEffect::DeactivateCompany { target } => {
|
||||||
if company_target_import_blocker(target, company_context).is_none() {
|
if company_target_allowed_for_import(
|
||||||
|
target,
|
||||||
|
company_context,
|
||||||
|
allow_condition_true_company,
|
||||||
|
) {
|
||||||
Ok(RuntimeEffect::DeactivateCompany {
|
Ok(RuntimeEffect::DeactivateCompany {
|
||||||
target: target.clone(),
|
target: target.clone(),
|
||||||
})
|
})
|
||||||
|
|
@ -962,7 +1115,11 @@ fn smp_runtime_effect_to_runtime_effect(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RuntimeEffect::SetCompanyTrackLayingCapacity { target, value } => {
|
RuntimeEffect::SetCompanyTrackLayingCapacity { target, value } => {
|
||||||
if company_target_import_blocker(target, company_context).is_none() {
|
if company_target_allowed_for_import(
|
||||||
|
target,
|
||||||
|
company_context,
|
||||||
|
allow_condition_true_company,
|
||||||
|
) {
|
||||||
Ok(RuntimeEffect::SetCompanyTrackLayingCapacity {
|
Ok(RuntimeEffect::SetCompanyTrackLayingCapacity {
|
||||||
target: target.clone(),
|
target: target.clone(),
|
||||||
value: *value,
|
value: *value,
|
||||||
|
|
@ -972,7 +1129,11 @@ fn smp_runtime_effect_to_runtime_effect(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RuntimeEffect::AdjustCompanyCash { target, delta } => {
|
RuntimeEffect::AdjustCompanyCash { target, delta } => {
|
||||||
if company_target_import_blocker(target, company_context).is_none() {
|
if company_target_allowed_for_import(
|
||||||
|
target,
|
||||||
|
company_context,
|
||||||
|
allow_condition_true_company,
|
||||||
|
) {
|
||||||
Ok(RuntimeEffect::AdjustCompanyCash {
|
Ok(RuntimeEffect::AdjustCompanyCash {
|
||||||
target: target.clone(),
|
target: target.clone(),
|
||||||
delta: *delta,
|
delta: *delta,
|
||||||
|
|
@ -982,7 +1143,11 @@ fn smp_runtime_effect_to_runtime_effect(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RuntimeEffect::AdjustCompanyDebt { target, delta } => {
|
RuntimeEffect::AdjustCompanyDebt { target, delta } => {
|
||||||
if company_target_import_blocker(target, company_context).is_none() {
|
if company_target_allowed_for_import(
|
||||||
|
target,
|
||||||
|
company_context,
|
||||||
|
allow_condition_true_company,
|
||||||
|
) {
|
||||||
Ok(RuntimeEffect::AdjustCompanyDebt {
|
Ok(RuntimeEffect::AdjustCompanyDebt {
|
||||||
target: target.clone(),
|
target: target.clone(),
|
||||||
delta: *delta,
|
delta: *delta,
|
||||||
|
|
@ -1007,6 +1172,7 @@ fn smp_runtime_effect_to_runtime_effect(
|
||||||
record: Box::new(smp_runtime_record_template_to_runtime(
|
record: Box::new(smp_runtime_record_template_to_runtime(
|
||||||
record,
|
record,
|
||||||
company_context,
|
company_context,
|
||||||
|
allow_condition_true_company,
|
||||||
)?),
|
)?),
|
||||||
}),
|
}),
|
||||||
RuntimeEffect::ActivateEventRecord { record_id } => {
|
RuntimeEffect::ActivateEventRecord { record_id } => {
|
||||||
|
|
@ -1028,6 +1194,7 @@ fn smp_runtime_effect_to_runtime_effect(
|
||||||
fn smp_runtime_record_template_to_runtime(
|
fn smp_runtime_record_template_to_runtime(
|
||||||
record: &RuntimeEventRecordTemplate,
|
record: &RuntimeEventRecordTemplate,
|
||||||
company_context: &ImportCompanyContext,
|
company_context: &ImportCompanyContext,
|
||||||
|
allow_condition_true_company: bool,
|
||||||
) -> Result<RuntimeEventRecordTemplate, String> {
|
) -> Result<RuntimeEventRecordTemplate, String> {
|
||||||
Ok(RuntimeEventRecordTemplate {
|
Ok(RuntimeEventRecordTemplate {
|
||||||
record_id: record.record_id,
|
record_id: record.record_id,
|
||||||
|
|
@ -1035,7 +1202,39 @@ fn smp_runtime_record_template_to_runtime(
|
||||||
active: record.active,
|
active: record.active,
|
||||||
marks_collection_dirty: record.marks_collection_dirty,
|
marks_collection_dirty: record.marks_collection_dirty,
|
||||||
one_shot: record.one_shot,
|
one_shot: record.one_shot,
|
||||||
effects: smp_runtime_effects_to_runtime_effects(&record.effects, company_context)?,
|
conditions: record.conditions.clone(),
|
||||||
|
effects: smp_runtime_effects_to_runtime_effects(
|
||||||
|
&record.effects,
|
||||||
|
company_context,
|
||||||
|
allow_condition_true_company,
|
||||||
|
)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn company_target_allowed_for_import(
|
||||||
|
target: &RuntimeCompanyTarget,
|
||||||
|
company_context: &ImportCompanyContext,
|
||||||
|
allow_condition_true_company: bool,
|
||||||
|
) -> bool {
|
||||||
|
match company_target_import_blocker(target, company_context) {
|
||||||
|
None => true,
|
||||||
|
Some(CompanyTargetImportBlocker::MissingConditionContext)
|
||||||
|
if allow_condition_true_company
|
||||||
|
&& matches!(target, RuntimeCompanyTarget::ConditionTrueCompany) =>
|
||||||
|
{
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Some(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn conditions_provide_company_context(conditions: &[RuntimeCondition]) -> bool {
|
||||||
|
conditions.iter().any(|condition| {
|
||||||
|
matches!(
|
||||||
|
condition,
|
||||||
|
RuntimeCondition::CompanyNumericThreshold { .. }
|
||||||
|
| RuntimeCondition::CompanyTerritoryNumericThreshold { .. }
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1105,6 +1304,15 @@ fn company_target_import_error_message(
|
||||||
"packed company effect requires territory runtime ownership for negative-sentinel scope"
|
"packed company effect requires territory runtime ownership for negative-sentinel scope"
|
||||||
.to_string()
|
.to_string()
|
||||||
}
|
}
|
||||||
|
Some(CompanyTargetImportBlocker::MissingTerritoryContext) => {
|
||||||
|
"packed condition requires territory runtime context".to_string()
|
||||||
|
}
|
||||||
|
Some(CompanyTargetImportBlocker::NamedTerritoryBinding) => {
|
||||||
|
"packed condition requires named territory binding".to_string()
|
||||||
|
}
|
||||||
|
Some(CompanyTargetImportBlocker::UnmappedOrdinaryCondition) => {
|
||||||
|
"packed ordinary condition is not yet mapped".to_string()
|
||||||
|
}
|
||||||
None => "packed company effect is importable".to_string(),
|
None => "packed company effect is importable".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1125,9 +1333,18 @@ fn determine_packed_event_import_outcome(
|
||||||
return "blocked_missing_compact_control".to_string();
|
return "blocked_missing_compact_control".to_string();
|
||||||
}
|
}
|
||||||
if !record.executable_import_ready {
|
if !record.executable_import_ready {
|
||||||
return "blocked_unmapped_real_descriptor".to_string();
|
return if record
|
||||||
|
.standalone_condition_rows
|
||||||
|
.iter()
|
||||||
|
.any(|row| row.raw_condition_id >= 0)
|
||||||
|
{
|
||||||
|
"blocked_unmapped_ordinary_condition".to_string()
|
||||||
|
} else {
|
||||||
|
"blocked_unmapped_real_descriptor".to_string()
|
||||||
|
};
|
||||||
}
|
}
|
||||||
if let Some(blocker) = packed_record_condition_scope_import_blocker(record) {
|
if let Some(blocker) = packed_record_condition_scope_import_blocker(record, company_context)
|
||||||
|
{
|
||||||
return company_target_import_outcome(blocker).to_string();
|
return company_target_import_outcome(blocker).to_string();
|
||||||
}
|
}
|
||||||
if let Some(blocker) = packed_record_company_target_import_blocker(record, company_context)
|
if let Some(blocker) = packed_record_company_target_import_blocker(record, company_context)
|
||||||
|
|
@ -1146,18 +1363,87 @@ fn packed_record_company_target_import_blocker(
|
||||||
record: &SmpLoadedPackedEventRecordSummary,
|
record: &SmpLoadedPackedEventRecordSummary,
|
||||||
company_context: &ImportCompanyContext,
|
company_context: &ImportCompanyContext,
|
||||||
) -> Option<CompanyTargetImportBlocker> {
|
) -> Option<CompanyTargetImportBlocker> {
|
||||||
let lowered_effects = match lowered_record_decoded_actions(record) {
|
if record
|
||||||
|
.decoded_actions
|
||||||
|
.iter()
|
||||||
|
.any(runtime_effect_uses_condition_true_company)
|
||||||
|
&& !record
|
||||||
|
.decoded_conditions
|
||||||
|
.iter()
|
||||||
|
.any(|condition| matches!(condition, RuntimeCondition::CompanyNumericThreshold { .. } | RuntimeCondition::CompanyTerritoryNumericThreshold { .. }))
|
||||||
|
{
|
||||||
|
return Some(CompanyTargetImportBlocker::MissingConditionContext);
|
||||||
|
}
|
||||||
|
let lowered_conditions = match lowered_record_decoded_conditions(record, company_context) {
|
||||||
|
Ok(conditions) => conditions,
|
||||||
|
Err(blocker) => return Some(blocker),
|
||||||
|
};
|
||||||
|
let has_company_condition_context = lowered_conditions.iter().any(|condition| {
|
||||||
|
matches!(
|
||||||
|
condition,
|
||||||
|
RuntimeCondition::CompanyNumericThreshold { .. }
|
||||||
|
| RuntimeCondition::CompanyTerritoryNumericThreshold { .. }
|
||||||
|
)
|
||||||
|
});
|
||||||
|
if let Some(blocker) = lowered_conditions.iter().find_map(|condition| {
|
||||||
|
runtime_condition_company_target_import_blocker(condition, company_context)
|
||||||
|
}) {
|
||||||
|
return Some(blocker);
|
||||||
|
}
|
||||||
|
let lowered_effects = match lowered_record_decoded_actions(record, company_context) {
|
||||||
Ok(effects) => effects,
|
Ok(effects) => effects,
|
||||||
Err(blocker) => return Some(blocker),
|
Err(blocker) => return Some(blocker),
|
||||||
};
|
};
|
||||||
lowered_effects
|
lowered_effects
|
||||||
.iter()
|
.iter()
|
||||||
.find_map(|effect| runtime_effect_company_target_import_blocker(effect, company_context))
|
.find_map(|effect| {
|
||||||
|
runtime_effect_company_target_import_blocker(
|
||||||
|
effect,
|
||||||
|
company_context,
|
||||||
|
has_company_condition_context,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn runtime_condition_company_target_import_blocker(
|
||||||
|
condition: &RuntimeCondition,
|
||||||
|
company_context: &ImportCompanyContext,
|
||||||
|
) -> Option<CompanyTargetImportBlocker> {
|
||||||
|
match condition {
|
||||||
|
RuntimeCondition::CompanyNumericThreshold { target, .. }
|
||||||
|
| RuntimeCondition::CompanyTerritoryNumericThreshold { target, .. } => {
|
||||||
|
company_target_import_blocker(target, company_context)
|
||||||
|
}
|
||||||
|
RuntimeCondition::TerritoryNumericThreshold { .. } => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn runtime_effect_uses_condition_true_company(effect: &RuntimeEffect) -> bool {
|
||||||
|
match effect {
|
||||||
|
RuntimeEffect::SetCompanyCash { target, .. }
|
||||||
|
| RuntimeEffect::DeactivateCompany { target }
|
||||||
|
| RuntimeEffect::SetCompanyTrackLayingCapacity { target, .. }
|
||||||
|
| RuntimeEffect::AdjustCompanyCash { target, .. }
|
||||||
|
| RuntimeEffect::AdjustCompanyDebt { target, .. } => {
|
||||||
|
matches!(target, RuntimeCompanyTarget::ConditionTrueCompany)
|
||||||
|
}
|
||||||
|
RuntimeEffect::AppendEventRecord { record } => record
|
||||||
|
.effects
|
||||||
|
.iter()
|
||||||
|
.any(runtime_effect_uses_condition_true_company),
|
||||||
|
RuntimeEffect::SetWorldFlag { .. }
|
||||||
|
| RuntimeEffect::SetCandidateAvailability { .. }
|
||||||
|
| RuntimeEffect::SetSpecialCondition { .. }
|
||||||
|
| RuntimeEffect::ActivateEventRecord { .. }
|
||||||
|
| RuntimeEffect::DeactivateEventRecord { .. }
|
||||||
|
| RuntimeEffect::RemoveEventRecord { .. } => false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn runtime_effect_company_target_import_blocker(
|
fn runtime_effect_company_target_import_blocker(
|
||||||
effect: &RuntimeEffect,
|
effect: &RuntimeEffect,
|
||||||
company_context: &ImportCompanyContext,
|
company_context: &ImportCompanyContext,
|
||||||
|
allow_condition_true_company: bool,
|
||||||
) -> Option<CompanyTargetImportBlocker> {
|
) -> Option<CompanyTargetImportBlocker> {
|
||||||
match effect {
|
match effect {
|
||||||
RuntimeEffect::SetCompanyCash { target, .. }
|
RuntimeEffect::SetCompanyCash { target, .. }
|
||||||
|
|
@ -1165,10 +1451,19 @@ fn runtime_effect_company_target_import_blocker(
|
||||||
| RuntimeEffect::SetCompanyTrackLayingCapacity { target, .. }
|
| RuntimeEffect::SetCompanyTrackLayingCapacity { target, .. }
|
||||||
| RuntimeEffect::AdjustCompanyCash { target, .. }
|
| RuntimeEffect::AdjustCompanyCash { target, .. }
|
||||||
| RuntimeEffect::AdjustCompanyDebt { target, .. } => {
|
| RuntimeEffect::AdjustCompanyDebt { target, .. } => {
|
||||||
|
if allow_condition_true_company
|
||||||
|
&& matches!(target, RuntimeCompanyTarget::ConditionTrueCompany)
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
company_target_import_blocker(target, company_context)
|
company_target_import_blocker(target, company_context)
|
||||||
}
|
}
|
||||||
RuntimeEffect::AppendEventRecord { record } => record.effects.iter().find_map(|nested| {
|
RuntimeEffect::AppendEventRecord { record } => record.effects.iter().find_map(|nested| {
|
||||||
runtime_effect_company_target_import_blocker(nested, company_context)
|
runtime_effect_company_target_import_blocker(
|
||||||
|
nested,
|
||||||
|
company_context,
|
||||||
|
allow_condition_true_company,
|
||||||
|
)
|
||||||
}),
|
}),
|
||||||
RuntimeEffect::SetWorldFlag { .. }
|
RuntimeEffect::SetWorldFlag { .. }
|
||||||
| RuntimeEffect::SetCandidateAvailability { .. }
|
| RuntimeEffect::SetCandidateAvailability { .. }
|
||||||
|
|
@ -1226,6 +1521,11 @@ fn company_target_import_outcome(blocker: CompanyTargetImportBlocker) -> &'stati
|
||||||
}
|
}
|
||||||
CompanyTargetImportBlocker::PlayerConditionScope => "blocked_player_condition_scope",
|
CompanyTargetImportBlocker::PlayerConditionScope => "blocked_player_condition_scope",
|
||||||
CompanyTargetImportBlocker::TerritoryConditionScope => "blocked_territory_condition_scope",
|
CompanyTargetImportBlocker::TerritoryConditionScope => "blocked_territory_condition_scope",
|
||||||
|
CompanyTargetImportBlocker::MissingTerritoryContext => "blocked_missing_territory_context",
|
||||||
|
CompanyTargetImportBlocker::NamedTerritoryBinding => "blocked_named_territory_binding",
|
||||||
|
CompanyTargetImportBlocker::UnmappedOrdinaryCondition => {
|
||||||
|
"blocked_unmapped_ordinary_condition"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1501,7 +1801,7 @@ fn resolve_document_path(base_dir: &Path, path: &str) -> PathBuf {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{StepCommand, execute_step_command};
|
use crate::{RuntimeTrackPieceCounts, StepCommand, execute_step_command};
|
||||||
|
|
||||||
fn state() -> RuntimeState {
|
fn state() -> RuntimeState {
|
||||||
RuntimeState {
|
RuntimeState {
|
||||||
|
|
@ -1517,6 +1817,8 @@ mod tests {
|
||||||
metadata: BTreeMap::new(),
|
metadata: BTreeMap::new(),
|
||||||
companies: Vec::new(),
|
companies: Vec::new(),
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
|
territories: Vec::new(),
|
||||||
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
@ -1573,6 +1875,11 @@ mod tests {
|
||||||
subtype: 4,
|
subtype: 4,
|
||||||
flag_bytes: vec![0x30; 25],
|
flag_bytes: vec![0x30; 25],
|
||||||
candidate_name: Some("AutoPlant".to_string()),
|
candidate_name: Some("AutoPlant".to_string()),
|
||||||
|
comparator: None,
|
||||||
|
metric: None,
|
||||||
|
semantic_family: None,
|
||||||
|
semantic_preview: None,
|
||||||
|
requires_candidate_name_binding: false,
|
||||||
notes: vec!["negative sentinel-style condition row id".to_string()],
|
notes: vec!["negative sentinel-style condition row id".to_string()],
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
@ -1600,6 +1907,7 @@ mod tests {
|
||||||
negative_sentinel_scope: None,
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||||
grouped_effect_rows: vec![],
|
grouped_effect_rows: vec![],
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
decoded_actions: vec![effect],
|
decoded_actions: vec![effect],
|
||||||
executable_import_ready: false,
|
executable_import_ready: false,
|
||||||
notes: vec!["synthetic test record".to_string()],
|
notes: vec!["synthetic test record".to_string()],
|
||||||
|
|
@ -1996,6 +2304,7 @@ mod tests {
|
||||||
negative_sentinel_scope: None,
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||||
grouped_effect_rows: Vec::new(),
|
grouped_effect_rows: Vec::new(),
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
decoded_actions: Vec::new(),
|
decoded_actions: Vec::new(),
|
||||||
executable_import_ready: false,
|
executable_import_ready: false,
|
||||||
notes: vec!["test".to_string()],
|
notes: vec!["test".to_string()],
|
||||||
|
|
@ -2018,6 +2327,7 @@ mod tests {
|
||||||
negative_sentinel_scope: None,
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||||
grouped_effect_rows: Vec::new(),
|
grouped_effect_rows: Vec::new(),
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
decoded_actions: Vec::new(),
|
decoded_actions: Vec::new(),
|
||||||
executable_import_ready: false,
|
executable_import_ready: false,
|
||||||
notes: vec!["test".to_string()],
|
notes: vec!["test".to_string()],
|
||||||
|
|
@ -2040,6 +2350,7 @@ mod tests {
|
||||||
negative_sentinel_scope: None,
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||||
grouped_effect_rows: Vec::new(),
|
grouped_effect_rows: Vec::new(),
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
decoded_actions: Vec::new(),
|
decoded_actions: Vec::new(),
|
||||||
executable_import_ready: false,
|
executable_import_ready: false,
|
||||||
notes: vec!["test".to_string()],
|
notes: vec!["test".to_string()],
|
||||||
|
|
@ -2252,6 +2563,7 @@ mod tests {
|
||||||
negative_sentinel_scope: None,
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts: vec![0, 1, 0, 0],
|
grouped_effect_row_counts: vec![0, 1, 0, 0],
|
||||||
grouped_effect_rows: vec![],
|
grouped_effect_rows: vec![],
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
decoded_actions: vec![
|
decoded_actions: vec![
|
||||||
RuntimeEffect::SetWorldFlag {
|
RuntimeEffect::SetWorldFlag {
|
||||||
key: "from_packed_root".to_string(),
|
key: "from_packed_root".to_string(),
|
||||||
|
|
@ -2264,6 +2576,7 @@ mod tests {
|
||||||
active: true,
|
active: true,
|
||||||
marks_collection_dirty: false,
|
marks_collection_dirty: false,
|
||||||
one_shot: false,
|
one_shot: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects: vec![RuntimeEffect::SetSpecialCondition {
|
effects: vec![RuntimeEffect::SetSpecialCondition {
|
||||||
label: "Imported Follow-On".to_string(),
|
label: "Imported Follow-On".to_string(),
|
||||||
value: 1,
|
value: 1,
|
||||||
|
|
@ -2363,6 +2676,7 @@ mod tests {
|
||||||
negative_sentinel_scope: None,
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||||
grouped_effect_rows: vec![],
|
grouped_effect_rows: vec![],
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
decoded_actions: vec![RuntimeEffect::AdjustCompanyCash {
|
decoded_actions: vec![RuntimeEffect::AdjustCompanyCash {
|
||||||
target: crate::RuntimeCompanyTarget::Ids { ids: vec![42] },
|
target: crate::RuntimeCompanyTarget::Ids { ids: vec![42] },
|
||||||
delta: 50,
|
delta: 50,
|
||||||
|
|
@ -2498,6 +2812,9 @@ mod tests {
|
||||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||||
current_cash: 100,
|
current_cash: 100,
|
||||||
debt: 10,
|
debt: 10,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
active: true,
|
active: true,
|
||||||
available_track_laying_capacity: None,
|
available_track_laying_capacity: None,
|
||||||
},
|
},
|
||||||
|
|
@ -2506,6 +2823,9 @@ mod tests {
|
||||||
controller_kind: RuntimeCompanyControllerKind::Ai,
|
controller_kind: RuntimeCompanyControllerKind::Ai,
|
||||||
current_cash: 50,
|
current_cash: 50,
|
||||||
debt: 20,
|
debt: 20,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
active: true,
|
active: true,
|
||||||
available_track_laying_capacity: None,
|
available_track_laying_capacity: None,
|
||||||
},
|
},
|
||||||
|
|
@ -2640,6 +2960,7 @@ mod tests {
|
||||||
)),
|
)),
|
||||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||||
grouped_effect_rows: real_grouped_rows(),
|
grouped_effect_rows: real_grouped_rows(),
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
||||||
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||||
value: 7,
|
value: 7,
|
||||||
|
|
@ -2694,6 +3015,9 @@ mod tests {
|
||||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||||
current_cash: 100,
|
current_cash: 100,
|
||||||
debt: 10,
|
debt: 10,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
active: true,
|
active: true,
|
||||||
available_track_laying_capacity: None,
|
available_track_laying_capacity: None,
|
||||||
},
|
},
|
||||||
|
|
@ -2702,6 +3026,9 @@ mod tests {
|
||||||
controller_kind: RuntimeCompanyControllerKind::Ai,
|
controller_kind: RuntimeCompanyControllerKind::Ai,
|
||||||
current_cash: 50,
|
current_cash: 50,
|
||||||
debt: 20,
|
debt: 20,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
active: true,
|
active: true,
|
||||||
available_track_laying_capacity: None,
|
available_track_laying_capacity: None,
|
||||||
},
|
},
|
||||||
|
|
@ -2710,6 +3037,9 @@ mod tests {
|
||||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||||
current_cash: 70,
|
current_cash: 70,
|
||||||
debt: 30,
|
debt: 30,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
active: true,
|
active: true,
|
||||||
available_track_laying_capacity: None,
|
available_track_laying_capacity: None,
|
||||||
},
|
},
|
||||||
|
|
@ -2763,6 +3093,7 @@ mod tests {
|
||||||
)),
|
)),
|
||||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||||
grouped_effect_rows: real_grouped_rows(),
|
grouped_effect_rows: real_grouped_rows(),
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
||||||
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||||
value: 7,
|
value: 7,
|
||||||
|
|
@ -2790,6 +3121,7 @@ mod tests {
|
||||||
)),
|
)),
|
||||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||||
grouped_effect_rows: real_grouped_rows(),
|
grouped_effect_rows: real_grouped_rows(),
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
||||||
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||||
value: 8,
|
value: 8,
|
||||||
|
|
@ -2817,6 +3149,7 @@ mod tests {
|
||||||
)),
|
)),
|
||||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||||
grouped_effect_rows: real_grouped_rows(),
|
grouped_effect_rows: real_grouped_rows(),
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
||||||
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||||
value: 9,
|
value: 9,
|
||||||
|
|
@ -2844,6 +3177,7 @@ mod tests {
|
||||||
)),
|
)),
|
||||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||||
grouped_effect_rows: real_grouped_rows(),
|
grouped_effect_rows: real_grouped_rows(),
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
||||||
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||||
value: 10,
|
value: 10,
|
||||||
|
|
@ -2871,6 +3205,7 @@ mod tests {
|
||||||
)),
|
)),
|
||||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||||
grouped_effect_rows: real_grouped_rows(),
|
grouped_effect_rows: real_grouped_rows(),
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
||||||
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||||
value: 11,
|
value: 11,
|
||||||
|
|
@ -2995,6 +3330,7 @@ mod tests {
|
||||||
negative_sentinel_scope: Some(player_negative_sentinel_scope()),
|
negative_sentinel_scope: Some(player_negative_sentinel_scope()),
|
||||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||||
grouped_effect_rows: real_grouped_rows(),
|
grouped_effect_rows: real_grouped_rows(),
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
||||||
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||||
value: 7,
|
value: 7,
|
||||||
|
|
@ -3069,6 +3405,7 @@ mod tests {
|
||||||
negative_sentinel_scope: Some(territory_negative_sentinel_scope()),
|
negative_sentinel_scope: Some(territory_negative_sentinel_scope()),
|
||||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||||
grouped_effect_rows: real_grouped_rows(),
|
grouped_effect_rows: real_grouped_rows(),
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
||||||
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||||
value: 7,
|
value: 7,
|
||||||
|
|
@ -3143,6 +3480,7 @@ mod tests {
|
||||||
negative_sentinel_scope: None,
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||||
grouped_effect_rows: real_grouped_rows(),
|
grouped_effect_rows: real_grouped_rows(),
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
decoded_actions: vec![],
|
decoded_actions: vec![],
|
||||||
executable_import_ready: false,
|
executable_import_ready: false,
|
||||||
notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()],
|
notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()],
|
||||||
|
|
@ -3187,10 +3525,15 @@ mod tests {
|
||||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||||
current_cash: 500,
|
current_cash: 500,
|
||||||
debt: 20,
|
debt: 20,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
active: true,
|
active: true,
|
||||||
available_track_laying_capacity: None,
|
available_track_laying_capacity: None,
|
||||||
}],
|
}],
|
||||||
selected_company_id: Some(42),
|
selected_company_id: Some(42),
|
||||||
|
territories: Vec::new(),
|
||||||
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
@ -3275,6 +3618,7 @@ mod tests {
|
||||||
"grouped effect row carries locomotive-name side string".to_string(),
|
"grouped effect row carries locomotive-name side string".to_string(),
|
||||||
],
|
],
|
||||||
}],
|
}],
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
||||||
target: RuntimeCompanyTarget::SelectedCompany,
|
target: RuntimeCompanyTarget::SelectedCompany,
|
||||||
value: 250,
|
value: 250,
|
||||||
|
|
@ -3324,6 +3668,9 @@ mod tests {
|
||||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||||
current_cash: 500,
|
current_cash: 500,
|
||||||
debt: 20,
|
debt: 20,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
active: true,
|
active: true,
|
||||||
available_track_laying_capacity: None,
|
available_track_laying_capacity: None,
|
||||||
}],
|
}],
|
||||||
|
|
@ -3384,6 +3731,7 @@ mod tests {
|
||||||
negative_sentinel_scope: None,
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||||
grouped_effect_rows: vec![real_deactivate_company_row(true)],
|
grouped_effect_rows: vec![real_deactivate_company_row(true)],
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
decoded_actions: vec![RuntimeEffect::DeactivateCompany {
|
decoded_actions: vec![RuntimeEffect::DeactivateCompany {
|
||||||
target: RuntimeCompanyTarget::SelectedCompany,
|
target: RuntimeCompanyTarget::SelectedCompany,
|
||||||
}],
|
}],
|
||||||
|
|
@ -3469,6 +3817,7 @@ mod tests {
|
||||||
negative_sentinel_scope: None,
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||||
grouped_effect_rows: vec![real_deactivate_company_row(false)],
|
grouped_effect_rows: vec![real_deactivate_company_row(false)],
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
decoded_actions: vec![],
|
decoded_actions: vec![],
|
||||||
executable_import_ready: false,
|
executable_import_ready: false,
|
||||||
notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()],
|
notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()],
|
||||||
|
|
@ -3503,6 +3852,9 @@ mod tests {
|
||||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||||
current_cash: 500,
|
current_cash: 500,
|
||||||
debt: 20,
|
debt: 20,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
active: true,
|
active: true,
|
||||||
available_track_laying_capacity: None,
|
available_track_laying_capacity: None,
|
||||||
}],
|
}],
|
||||||
|
|
@ -3563,6 +3915,7 @@ mod tests {
|
||||||
negative_sentinel_scope: None,
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||||
grouped_effect_rows: vec![real_track_capacity_row(18)],
|
grouped_effect_rows: vec![real_track_capacity_row(18)],
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
decoded_actions: vec![RuntimeEffect::SetCompanyTrackLayingCapacity {
|
decoded_actions: vec![RuntimeEffect::SetCompanyTrackLayingCapacity {
|
||||||
target: RuntimeCompanyTarget::SelectedCompany,
|
target: RuntimeCompanyTarget::SelectedCompany,
|
||||||
value: Some(18),
|
value: Some(18),
|
||||||
|
|
@ -3602,6 +3955,9 @@ mod tests {
|
||||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||||
current_cash: 500,
|
current_cash: 500,
|
||||||
debt: 20,
|
debt: 20,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
active: true,
|
active: true,
|
||||||
available_track_laying_capacity: None,
|
available_track_laying_capacity: None,
|
||||||
}],
|
}],
|
||||||
|
|
@ -3665,6 +4021,7 @@ mod tests {
|
||||||
real_track_capacity_row(18),
|
real_track_capacity_row(18),
|
||||||
unsupported_real_grouped_row(),
|
unsupported_real_grouped_row(),
|
||||||
],
|
],
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
decoded_actions: vec![RuntimeEffect::SetCompanyTrackLayingCapacity {
|
decoded_actions: vec![RuntimeEffect::SetCompanyTrackLayingCapacity {
|
||||||
target: RuntimeCompanyTarget::SelectedCompany,
|
target: RuntimeCompanyTarget::SelectedCompany,
|
||||||
value: Some(18),
|
value: Some(18),
|
||||||
|
|
@ -3713,10 +4070,15 @@ mod tests {
|
||||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||||
current_cash: 500,
|
current_cash: 500,
|
||||||
debt: 20,
|
debt: 20,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
active: true,
|
active: true,
|
||||||
available_track_laying_capacity: None,
|
available_track_laying_capacity: None,
|
||||||
}],
|
}],
|
||||||
selected_company_id: Some(42),
|
selected_company_id: Some(42),
|
||||||
|
territories: Vec::new(),
|
||||||
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: vec![RuntimeEventRecord {
|
event_runtime_records: vec![RuntimeEventRecord {
|
||||||
record_id: 1,
|
record_id: 1,
|
||||||
|
|
@ -3726,6 +4088,7 @@ mod tests {
|
||||||
marks_collection_dirty: false,
|
marks_collection_dirty: false,
|
||||||
one_shot: false,
|
one_shot: false,
|
||||||
has_fired: false,
|
has_fired: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects: vec![],
|
effects: vec![],
|
||||||
}],
|
}],
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
@ -3780,6 +4143,7 @@ mod tests {
|
||||||
negative_sentinel_scope: None,
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||||
grouped_effect_rows: vec![],
|
grouped_effect_rows: vec![],
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
decoded_actions: vec![RuntimeEffect::AdjustCompanyCash {
|
decoded_actions: vec![RuntimeEffect::AdjustCompanyCash {
|
||||||
target: crate::RuntimeCompanyTarget::Ids { ids: vec![42] },
|
target: crate::RuntimeCompanyTarget::Ids { ids: vec![42] },
|
||||||
delta: 50,
|
delta: 50,
|
||||||
|
|
@ -3878,10 +4242,15 @@ mod tests {
|
||||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||||
current_cash: 100,
|
current_cash: 100,
|
||||||
debt: 0,
|
debt: 0,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
active: true,
|
active: true,
|
||||||
available_track_laying_capacity: None,
|
available_track_laying_capacity: None,
|
||||||
}],
|
}],
|
||||||
selected_company_id: Some(42),
|
selected_company_id: Some(42),
|
||||||
|
territories: Vec::new(),
|
||||||
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
@ -3939,6 +4308,7 @@ mod tests {
|
||||||
negative_sentinel_scope: None,
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||||
grouped_effect_rows: vec![],
|
grouped_effect_rows: vec![],
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
decoded_actions: vec![RuntimeEffect::AdjustCompanyCash {
|
decoded_actions: vec![RuntimeEffect::AdjustCompanyCash {
|
||||||
target: crate::RuntimeCompanyTarget::Ids { ids: vec![42] },
|
target: crate::RuntimeCompanyTarget::Ids { ids: vec![42] },
|
||||||
delta: 50,
|
delta: 50,
|
||||||
|
|
|
||||||
|
|
@ -36,12 +36,15 @@ pub use pk4::{
|
||||||
};
|
};
|
||||||
pub use runtime::{
|
pub use runtime::{
|
||||||
RuntimeCompany, RuntimeCompanyConditionTestScope, RuntimeCompanyControllerKind,
|
RuntimeCompany, RuntimeCompanyConditionTestScope, RuntimeCompanyControllerKind,
|
||||||
RuntimeCompanyTarget, RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate,
|
RuntimeCompanyMetric, RuntimeCompanyTarget, RuntimeCompanyTerritoryTrackPieceCount,
|
||||||
RuntimePackedEventCollectionSummary, RuntimePackedEventCompactControlSummary,
|
RuntimeCondition, RuntimeConditionComparator, RuntimeEffect, RuntimeEventRecord,
|
||||||
RuntimePackedEventConditionRowSummary, RuntimePackedEventGroupedEffectRowSummary,
|
RuntimeEventRecordTemplate, RuntimePackedEventCollectionSummary,
|
||||||
RuntimePackedEventNegativeSentinelScopeSummary, RuntimePackedEventRecordSummary,
|
RuntimePackedEventCompactControlSummary, RuntimePackedEventConditionRowSummary,
|
||||||
RuntimePackedEventTextBandSummary, RuntimePlayerConditionTestScope, RuntimeSaveProfileState,
|
RuntimePackedEventGroupedEffectRowSummary, RuntimePackedEventNegativeSentinelScopeSummary,
|
||||||
RuntimeServiceState, RuntimeState, RuntimeWorldRestoreState,
|
RuntimePackedEventRecordSummary, RuntimePackedEventTextBandSummary,
|
||||||
|
RuntimePlayerConditionTestScope, RuntimeSaveProfileState, RuntimeServiceState, RuntimeState,
|
||||||
|
RuntimeTerritory, RuntimeTerritoryMetric, RuntimeTrackMetric, RuntimeTrackPieceCounts,
|
||||||
|
RuntimeWorldRestoreState,
|
||||||
};
|
};
|
||||||
pub use smp::{
|
pub use smp::{
|
||||||
SMP_FOUR_SIDECAR_BYTE_PLANES_MIN_BUNDLE_VERSION, SmpAlignedRuntimeRuleBandLane,
|
SMP_FOUR_SIDECAR_BYTE_PLANES_MIN_BUNDLE_VERSION, SmpAlignedRuntimeRuleBandLane,
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,8 @@ mod tests {
|
||||||
metadata: BTreeMap::new(),
|
metadata: BTreeMap::new(),
|
||||||
companies: Vec::new(),
|
companies: Vec::new(),
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
|
territories: Vec::new(),
|
||||||
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
|
||||||
|
|
@ -22,12 +22,49 @@ pub struct RuntimeCompany {
|
||||||
pub company_id: u32,
|
pub company_id: u32,
|
||||||
pub current_cash: i64,
|
pub current_cash: i64,
|
||||||
pub debt: u64,
|
pub debt: u64,
|
||||||
|
#[serde(default)]
|
||||||
|
pub credit_rating_score: Option<i64>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub prime_rate: Option<i64>,
|
||||||
#[serde(default = "runtime_company_default_active")]
|
#[serde(default = "runtime_company_default_active")]
|
||||||
pub active: bool,
|
pub active: bool,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub available_track_laying_capacity: Option<u32>,
|
pub available_track_laying_capacity: Option<u32>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub controller_kind: RuntimeCompanyControllerKind,
|
pub controller_kind: RuntimeCompanyControllerKind,
|
||||||
|
#[serde(default)]
|
||||||
|
pub track_piece_counts: RuntimeTrackPieceCounts,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||||
|
pub struct RuntimeTrackPieceCounts {
|
||||||
|
#[serde(default)]
|
||||||
|
pub total: u32,
|
||||||
|
#[serde(default)]
|
||||||
|
pub single: u32,
|
||||||
|
#[serde(default)]
|
||||||
|
pub double: u32,
|
||||||
|
#[serde(default)]
|
||||||
|
pub transition: u32,
|
||||||
|
#[serde(default)]
|
||||||
|
pub electric: u32,
|
||||||
|
#[serde(default)]
|
||||||
|
pub non_electric: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct RuntimeTerritory {
|
||||||
|
pub territory_id: u32,
|
||||||
|
#[serde(default)]
|
||||||
|
pub track_piece_counts: RuntimeTrackPieceCounts,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct RuntimeCompanyTerritoryTrackPieceCount {
|
||||||
|
pub company_id: u32,
|
||||||
|
pub territory_id: u32,
|
||||||
|
#[serde(default)]
|
||||||
|
pub track_piece_counts: RuntimeTrackPieceCounts,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
|
@ -63,6 +100,76 @@ pub enum RuntimePlayerConditionTestScope {
|
||||||
HumanPlayersOnly,
|
HumanPlayersOnly,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum RuntimeConditionComparator {
|
||||||
|
Ge,
|
||||||
|
Le,
|
||||||
|
Gt,
|
||||||
|
Lt,
|
||||||
|
Eq,
|
||||||
|
Ne,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum RuntimeCompanyMetric {
|
||||||
|
CurrentCash,
|
||||||
|
TotalDebt,
|
||||||
|
CreditRating,
|
||||||
|
PrimeRate,
|
||||||
|
TrackPiecesTotal,
|
||||||
|
TrackPiecesSingle,
|
||||||
|
TrackPiecesDouble,
|
||||||
|
TrackPiecesTransition,
|
||||||
|
TrackPiecesElectric,
|
||||||
|
TrackPiecesNonElectric,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum RuntimeTerritoryMetric {
|
||||||
|
TrackPiecesTotal,
|
||||||
|
TrackPiecesSingle,
|
||||||
|
TrackPiecesDouble,
|
||||||
|
TrackPiecesTransition,
|
||||||
|
TrackPiecesElectric,
|
||||||
|
TrackPiecesNonElectric,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum RuntimeTrackMetric {
|
||||||
|
Total,
|
||||||
|
Single,
|
||||||
|
Double,
|
||||||
|
Transition,
|
||||||
|
Electric,
|
||||||
|
NonElectric,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||||
|
pub enum RuntimeCondition {
|
||||||
|
CompanyNumericThreshold {
|
||||||
|
target: RuntimeCompanyTarget,
|
||||||
|
metric: RuntimeCompanyMetric,
|
||||||
|
comparator: RuntimeConditionComparator,
|
||||||
|
value: i64,
|
||||||
|
},
|
||||||
|
TerritoryNumericThreshold {
|
||||||
|
metric: RuntimeTerritoryMetric,
|
||||||
|
comparator: RuntimeConditionComparator,
|
||||||
|
value: i64,
|
||||||
|
},
|
||||||
|
CompanyTerritoryNumericThreshold {
|
||||||
|
target: RuntimeCompanyTarget,
|
||||||
|
metric: RuntimeTrackMetric,
|
||||||
|
comparator: RuntimeConditionComparator,
|
||||||
|
value: i64,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||||
pub enum RuntimeEffect {
|
pub enum RuntimeEffect {
|
||||||
|
|
@ -121,6 +228,8 @@ pub struct RuntimeEventRecordTemplate {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub one_shot: bool,
|
pub one_shot: bool,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub conditions: Vec<RuntimeCondition>,
|
||||||
|
#[serde(default)]
|
||||||
pub effects: Vec<RuntimeEffect>,
|
pub effects: Vec<RuntimeEffect>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -138,6 +247,8 @@ pub struct RuntimeEventRecord {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub has_fired: bool,
|
pub has_fired: bool,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub conditions: Vec<RuntimeCondition>,
|
||||||
|
#[serde(default)]
|
||||||
pub effects: Vec<RuntimeEffect>,
|
pub effects: Vec<RuntimeEffect>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -197,6 +308,8 @@ pub struct RuntimePackedEventRecordSummary {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub grouped_company_targets: Vec<Option<RuntimeCompanyTarget>>,
|
pub grouped_company_targets: Vec<Option<RuntimeCompanyTarget>>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub decoded_conditions: Vec<RuntimeCondition>,
|
||||||
|
#[serde(default)]
|
||||||
pub decoded_actions: Vec<RuntimeEffect>,
|
pub decoded_actions: Vec<RuntimeEffect>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub executable_import_ready: bool,
|
pub executable_import_ready: bool,
|
||||||
|
|
@ -250,6 +363,16 @@ pub struct RuntimePackedEventConditionRowSummary {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub candidate_name: Option<String>,
|
pub candidate_name: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub comparator: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub metric: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub semantic_family: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub semantic_preview: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub requires_candidate_name_binding: bool,
|
||||||
|
#[serde(default)]
|
||||||
pub notes: Vec<String>,
|
pub notes: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -293,6 +416,7 @@ impl RuntimeEventRecordTemplate {
|
||||||
marks_collection_dirty: self.marks_collection_dirty,
|
marks_collection_dirty: self.marks_collection_dirty,
|
||||||
one_shot: self.one_shot,
|
one_shot: self.one_shot,
|
||||||
has_fired: false,
|
has_fired: false,
|
||||||
|
conditions: self.conditions,
|
||||||
effects: self.effects,
|
effects: self.effects,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -384,6 +508,10 @@ pub struct RuntimeState {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub selected_company_id: Option<u32>,
|
pub selected_company_id: Option<u32>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub territories: Vec<RuntimeTerritory>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub company_territory_track_piece_counts: Vec<RuntimeCompanyTerritoryTrackPieceCount>,
|
||||||
|
#[serde(default)]
|
||||||
pub packed_event_collection: Option<RuntimePackedEventCollectionSummary>,
|
pub packed_event_collection: Option<RuntimePackedEventCollectionSummary>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub event_runtime_records: Vec<RuntimeEventRecord>,
|
pub event_runtime_records: Vec<RuntimeEventRecord>,
|
||||||
|
|
@ -424,11 +552,40 @@ impl RuntimeState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut seen_territory_ids = BTreeSet::new();
|
||||||
|
for territory in &self.territories {
|
||||||
|
if !seen_territory_ids.insert(territory.territory_id) {
|
||||||
|
return Err(format!("duplicate territory_id {}", territory.territory_id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for entry in &self.company_territory_track_piece_counts {
|
||||||
|
if !seen_company_ids.contains(&entry.company_id) {
|
||||||
|
return Err(format!(
|
||||||
|
"company_territory_track_piece_counts references unknown company_id {}",
|
||||||
|
entry.company_id
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if !seen_territory_ids.contains(&entry.territory_id) {
|
||||||
|
return Err(format!(
|
||||||
|
"company_territory_track_piece_counts references unknown territory_id {}",
|
||||||
|
entry.territory_id
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut seen_record_ids = BTreeSet::new();
|
let mut seen_record_ids = BTreeSet::new();
|
||||||
for record in &self.event_runtime_records {
|
for record in &self.event_runtime_records {
|
||||||
if !seen_record_ids.insert(record.record_id) {
|
if !seen_record_ids.insert(record.record_id) {
|
||||||
return Err(format!("duplicate record_id {}", record.record_id));
|
return Err(format!("duplicate record_id {}", record.record_id));
|
||||||
}
|
}
|
||||||
|
for (condition_index, condition) in record.conditions.iter().enumerate() {
|
||||||
|
validate_runtime_condition(condition, &seen_company_ids).map_err(|err| {
|
||||||
|
format!(
|
||||||
|
"event_runtime_records[record_id={}].conditions[{condition_index}] {err}",
|
||||||
|
record.record_id
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
}
|
||||||
for (effect_index, effect) in record.effects.iter().enumerate() {
|
for (effect_index, effect) in record.effects.iter().enumerate() {
|
||||||
validate_runtime_effect(effect, &seen_company_ids).map_err(|err| {
|
validate_runtime_effect(effect, &seen_company_ids).map_err(|err| {
|
||||||
format!(
|
format!(
|
||||||
|
|
@ -613,6 +770,42 @@ impl RuntimeState {
|
||||||
"packed_event_collection.records[{record_index}].standalone_condition_rows contains an empty candidate_name"
|
"packed_event_collection.records[{record_index}].standalone_condition_rows contains an empty candidate_name"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
if row
|
||||||
|
.comparator
|
||||||
|
.as_deref()
|
||||||
|
.is_some_and(|value| value.trim().is_empty())
|
||||||
|
{
|
||||||
|
return Err(format!(
|
||||||
|
"packed_event_collection.records[{record_index}].standalone_condition_rows contains an empty comparator"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if row
|
||||||
|
.metric
|
||||||
|
.as_deref()
|
||||||
|
.is_some_and(|value| value.trim().is_empty())
|
||||||
|
{
|
||||||
|
return Err(format!(
|
||||||
|
"packed_event_collection.records[{record_index}].standalone_condition_rows contains an empty metric"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if row
|
||||||
|
.semantic_family
|
||||||
|
.as_deref()
|
||||||
|
.is_some_and(|value| value.trim().is_empty())
|
||||||
|
{
|
||||||
|
return Err(format!(
|
||||||
|
"packed_event_collection.records[{record_index}].standalone_condition_rows contains an empty semantic_family"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if row
|
||||||
|
.semantic_preview
|
||||||
|
.as_deref()
|
||||||
|
.is_some_and(|value| value.trim().is_empty())
|
||||||
|
{
|
||||||
|
return Err(format!(
|
||||||
|
"packed_event_collection.records[{record_index}].standalone_condition_rows contains an empty semantic_preview"
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for row in &record.grouped_effect_rows {
|
for row in &record.grouped_effect_rows {
|
||||||
if row.row_shape.trim().is_empty() {
|
if row.row_shape.trim().is_empty() {
|
||||||
|
|
@ -758,6 +951,14 @@ fn validate_event_record_template(
|
||||||
record: &RuntimeEventRecordTemplate,
|
record: &RuntimeEventRecordTemplate,
|
||||||
valid_company_ids: &BTreeSet<u32>,
|
valid_company_ids: &BTreeSet<u32>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
|
for (condition_index, condition) in record.conditions.iter().enumerate() {
|
||||||
|
validate_runtime_condition(condition, valid_company_ids).map_err(|err| {
|
||||||
|
format!(
|
||||||
|
"template record_id={}.conditions[{condition_index}] {err}",
|
||||||
|
record.record_id
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
}
|
||||||
for (effect_index, effect) in record.effects.iter().enumerate() {
|
for (effect_index, effect) in record.effects.iter().enumerate() {
|
||||||
validate_runtime_effect(effect, valid_company_ids).map_err(|err| {
|
validate_runtime_effect(effect, valid_company_ids).map_err(|err| {
|
||||||
format!(
|
format!(
|
||||||
|
|
@ -770,6 +971,19 @@ fn validate_event_record_template(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validate_runtime_condition(
|
||||||
|
condition: &RuntimeCondition,
|
||||||
|
valid_company_ids: &BTreeSet<u32>,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
match condition {
|
||||||
|
RuntimeCondition::CompanyNumericThreshold { target, .. }
|
||||||
|
| RuntimeCondition::CompanyTerritoryNumericThreshold { target, .. } => {
|
||||||
|
validate_company_target(target, valid_company_ids)
|
||||||
|
}
|
||||||
|
RuntimeCondition::TerritoryNumericThreshold { .. } => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn validate_company_target(
|
fn validate_company_target(
|
||||||
target: &RuntimeCompanyTarget,
|
target: &RuntimeCompanyTarget,
|
||||||
valid_company_ids: &BTreeSet<u32>,
|
valid_company_ids: &BTreeSet<u32>,
|
||||||
|
|
@ -816,6 +1030,9 @@ mod tests {
|
||||||
company_id: 1,
|
company_id: 1,
|
||||||
current_cash: 100,
|
current_cash: 100,
|
||||||
debt: 0,
|
debt: 0,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
active: true,
|
active: true,
|
||||||
available_track_laying_capacity: None,
|
available_track_laying_capacity: None,
|
||||||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||||
|
|
@ -824,12 +1041,17 @@ mod tests {
|
||||||
company_id: 1,
|
company_id: 1,
|
||||||
current_cash: 200,
|
current_cash: 200,
|
||||||
debt: 0,
|
debt: 0,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
active: true,
|
active: true,
|
||||||
available_track_laying_capacity: None,
|
available_track_laying_capacity: None,
|
||||||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
|
territories: Vec::new(),
|
||||||
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
@ -877,6 +1099,8 @@ mod tests {
|
||||||
metadata: BTreeMap::new(),
|
metadata: BTreeMap::new(),
|
||||||
companies: Vec::new(),
|
companies: Vec::new(),
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
|
territories: Vec::new(),
|
||||||
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
@ -904,11 +1128,16 @@ mod tests {
|
||||||
company_id: 1,
|
company_id: 1,
|
||||||
current_cash: 100,
|
current_cash: 100,
|
||||||
debt: 0,
|
debt: 0,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
active: true,
|
active: true,
|
||||||
available_track_laying_capacity: None,
|
available_track_laying_capacity: None,
|
||||||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||||
}],
|
}],
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
|
territories: Vec::new(),
|
||||||
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: vec![RuntimeEventRecord {
|
event_runtime_records: vec![RuntimeEventRecord {
|
||||||
record_id: 7,
|
record_id: 7,
|
||||||
|
|
@ -918,6 +1147,7 @@ mod tests {
|
||||||
marks_collection_dirty: false,
|
marks_collection_dirty: false,
|
||||||
one_shot: false,
|
one_shot: false,
|
||||||
has_fired: false,
|
has_fired: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects: vec![RuntimeEffect::AdjustCompanyCash {
|
effects: vec![RuntimeEffect::AdjustCompanyCash {
|
||||||
target: RuntimeCompanyTarget::Ids { ids: vec![2] },
|
target: RuntimeCompanyTarget::Ids { ids: vec![2] },
|
||||||
delta: 50,
|
delta: 50,
|
||||||
|
|
@ -948,11 +1178,16 @@ mod tests {
|
||||||
company_id: 1,
|
company_id: 1,
|
||||||
current_cash: 100,
|
current_cash: 100,
|
||||||
debt: 0,
|
debt: 0,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
active: true,
|
active: true,
|
||||||
available_track_laying_capacity: None,
|
available_track_laying_capacity: None,
|
||||||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||||
}],
|
}],
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
|
territories: Vec::new(),
|
||||||
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: vec![RuntimeEventRecord {
|
event_runtime_records: vec![RuntimeEventRecord {
|
||||||
record_id: 7,
|
record_id: 7,
|
||||||
|
|
@ -962,6 +1197,7 @@ mod tests {
|
||||||
marks_collection_dirty: false,
|
marks_collection_dirty: false,
|
||||||
one_shot: false,
|
one_shot: false,
|
||||||
has_fired: false,
|
has_fired: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects: vec![RuntimeEffect::AppendEventRecord {
|
effects: vec![RuntimeEffect::AppendEventRecord {
|
||||||
record: Box::new(RuntimeEventRecordTemplate {
|
record: Box::new(RuntimeEventRecordTemplate {
|
||||||
record_id: 8,
|
record_id: 8,
|
||||||
|
|
@ -969,6 +1205,7 @@ mod tests {
|
||||||
active: true,
|
active: true,
|
||||||
marks_collection_dirty: false,
|
marks_collection_dirty: false,
|
||||||
one_shot: false,
|
one_shot: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects: vec![RuntimeEffect::AdjustCompanyCash {
|
effects: vec![RuntimeEffect::AdjustCompanyCash {
|
||||||
target: RuntimeCompanyTarget::Ids { ids: vec![2] },
|
target: RuntimeCompanyTarget::Ids { ids: vec![2] },
|
||||||
delta: 50,
|
delta: 50,
|
||||||
|
|
@ -999,6 +1236,8 @@ mod tests {
|
||||||
metadata: BTreeMap::new(),
|
metadata: BTreeMap::new(),
|
||||||
companies: Vec::new(),
|
companies: Vec::new(),
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
|
territories: Vec::new(),
|
||||||
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
packed_event_collection: Some(RuntimePackedEventCollectionSummary {
|
packed_event_collection: Some(RuntimePackedEventCollectionSummary {
|
||||||
source_kind: "packed-event-runtime-collection".to_string(),
|
source_kind: "packed-event-runtime-collection".to_string(),
|
||||||
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
||||||
|
|
@ -1031,6 +1270,7 @@ mod tests {
|
||||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||||
grouped_effect_rows: Vec::new(),
|
grouped_effect_rows: Vec::new(),
|
||||||
grouped_company_targets: Vec::new(),
|
grouped_company_targets: Vec::new(),
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
decoded_actions: Vec::new(),
|
decoded_actions: Vec::new(),
|
||||||
executable_import_ready: false,
|
executable_import_ready: false,
|
||||||
import_outcome: None,
|
import_outcome: None,
|
||||||
|
|
@ -1055,6 +1295,7 @@ mod tests {
|
||||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||||
grouped_effect_rows: Vec::new(),
|
grouped_effect_rows: Vec::new(),
|
||||||
grouped_company_targets: Vec::new(),
|
grouped_company_targets: Vec::new(),
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
decoded_actions: Vec::new(),
|
decoded_actions: Vec::new(),
|
||||||
executable_import_ready: false,
|
executable_import_ready: false,
|
||||||
import_outcome: None,
|
import_outcome: None,
|
||||||
|
|
@ -1088,11 +1329,16 @@ mod tests {
|
||||||
company_id: 1,
|
company_id: 1,
|
||||||
current_cash: 100,
|
current_cash: 100,
|
||||||
debt: 0,
|
debt: 0,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
active: true,
|
active: true,
|
||||||
available_track_laying_capacity: None,
|
available_track_laying_capacity: None,
|
||||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||||
}],
|
}],
|
||||||
selected_company_id: Some(2),
|
selected_company_id: Some(2),
|
||||||
|
territories: Vec::new(),
|
||||||
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
@ -1120,11 +1366,16 @@ mod tests {
|
||||||
company_id: 1,
|
company_id: 1,
|
||||||
current_cash: 100,
|
current_cash: 100,
|
||||||
debt: 0,
|
debt: 0,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
active: false,
|
active: false,
|
||||||
available_track_laying_capacity: None,
|
available_track_laying_capacity: None,
|
||||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||||
}],
|
}],
|
||||||
selected_company_id: Some(1),
|
selected_company_id: Some(1),
|
||||||
|
territories: Vec::new(),
|
||||||
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,9 @@ use serde::{Deserialize, Serialize};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
RuntimeCompanyConditionTestScope, RuntimeCompanyTarget, RuntimeEffect,
|
RuntimeCompanyConditionTestScope, RuntimeCompanyMetric, RuntimeCompanyTarget,
|
||||||
RuntimeEventRecordTemplate, RuntimePlayerConditionTestScope,
|
RuntimeCondition, RuntimeConditionComparator, RuntimeEffect, RuntimeEventRecordTemplate,
|
||||||
|
RuntimePlayerConditionTestScope, RuntimeTerritoryMetric, RuntimeTrackMetric,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const SMP_FOUR_SIDECAR_BYTE_PLANES_MIN_BUNDLE_VERSION: u32 = 0x03ec;
|
pub const SMP_FOUR_SIDECAR_BYTE_PLANES_MIN_BUNDLE_VERSION: u32 = 0x03ec;
|
||||||
|
|
@ -184,6 +185,133 @@ const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetad
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum RealOrdinaryConditionMetric {
|
||||||
|
Company(RuntimeCompanyMetric),
|
||||||
|
Territory(RuntimeTerritoryMetric),
|
||||||
|
CompanyTerritory(RuntimeTrackMetric),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
struct RealOrdinaryConditionMetadata {
|
||||||
|
raw_condition_id: i32,
|
||||||
|
label: &'static str,
|
||||||
|
metric: RealOrdinaryConditionMetric,
|
||||||
|
}
|
||||||
|
|
||||||
|
const REAL_ORDINARY_CONDITION_METADATA: [RealOrdinaryConditionMetadata; 22] = [
|
||||||
|
RealOrdinaryConditionMetadata {
|
||||||
|
raw_condition_id: 1802,
|
||||||
|
label: "Current Cash",
|
||||||
|
metric: RealOrdinaryConditionMetric::Company(RuntimeCompanyMetric::CurrentCash),
|
||||||
|
},
|
||||||
|
RealOrdinaryConditionMetadata {
|
||||||
|
raw_condition_id: 951,
|
||||||
|
label: "Total Debt",
|
||||||
|
metric: RealOrdinaryConditionMetric::Company(RuntimeCompanyMetric::TotalDebt),
|
||||||
|
},
|
||||||
|
RealOrdinaryConditionMetadata {
|
||||||
|
raw_condition_id: 2366,
|
||||||
|
label: "Credit Rating",
|
||||||
|
metric: RealOrdinaryConditionMetric::Company(RuntimeCompanyMetric::CreditRating),
|
||||||
|
},
|
||||||
|
RealOrdinaryConditionMetadata {
|
||||||
|
raw_condition_id: 2368,
|
||||||
|
label: "Prime Rate",
|
||||||
|
metric: RealOrdinaryConditionMetric::Company(RuntimeCompanyMetric::PrimeRate),
|
||||||
|
},
|
||||||
|
RealOrdinaryConditionMetadata {
|
||||||
|
raw_condition_id: 2293,
|
||||||
|
label: "Company Track Pieces",
|
||||||
|
metric: RealOrdinaryConditionMetric::Company(RuntimeCompanyMetric::TrackPiecesTotal),
|
||||||
|
},
|
||||||
|
RealOrdinaryConditionMetadata {
|
||||||
|
raw_condition_id: 2294,
|
||||||
|
label: "Company Single Track Pieces",
|
||||||
|
metric: RealOrdinaryConditionMetric::Company(RuntimeCompanyMetric::TrackPiecesSingle),
|
||||||
|
},
|
||||||
|
RealOrdinaryConditionMetadata {
|
||||||
|
raw_condition_id: 2295,
|
||||||
|
label: "Company Double Track Pieces",
|
||||||
|
metric: RealOrdinaryConditionMetric::Company(RuntimeCompanyMetric::TrackPiecesDouble),
|
||||||
|
},
|
||||||
|
RealOrdinaryConditionMetadata {
|
||||||
|
raw_condition_id: 2296,
|
||||||
|
label: "Company Transition Track Pieces",
|
||||||
|
metric: RealOrdinaryConditionMetric::Company(RuntimeCompanyMetric::TrackPiecesTransition),
|
||||||
|
},
|
||||||
|
RealOrdinaryConditionMetadata {
|
||||||
|
raw_condition_id: 2297,
|
||||||
|
label: "Company Electric Track Pieces",
|
||||||
|
metric: RealOrdinaryConditionMetric::Company(RuntimeCompanyMetric::TrackPiecesElectric),
|
||||||
|
},
|
||||||
|
RealOrdinaryConditionMetadata {
|
||||||
|
raw_condition_id: 2298,
|
||||||
|
label: "Company Non-Electric Track Pieces",
|
||||||
|
metric: RealOrdinaryConditionMetric::Company(RuntimeCompanyMetric::TrackPiecesNonElectric),
|
||||||
|
},
|
||||||
|
RealOrdinaryConditionMetadata {
|
||||||
|
raw_condition_id: 2313,
|
||||||
|
label: "Territory Track Pieces",
|
||||||
|
metric: RealOrdinaryConditionMetric::Territory(RuntimeTerritoryMetric::TrackPiecesTotal),
|
||||||
|
},
|
||||||
|
RealOrdinaryConditionMetadata {
|
||||||
|
raw_condition_id: 2314,
|
||||||
|
label: "Territory Single Track Pieces",
|
||||||
|
metric: RealOrdinaryConditionMetric::Territory(RuntimeTerritoryMetric::TrackPiecesSingle),
|
||||||
|
},
|
||||||
|
RealOrdinaryConditionMetadata {
|
||||||
|
raw_condition_id: 2315,
|
||||||
|
label: "Territory Double Track Pieces",
|
||||||
|
metric: RealOrdinaryConditionMetric::Territory(RuntimeTerritoryMetric::TrackPiecesDouble),
|
||||||
|
},
|
||||||
|
RealOrdinaryConditionMetadata {
|
||||||
|
raw_condition_id: 2316,
|
||||||
|
label: "Territory Transition Track Pieces",
|
||||||
|
metric: RealOrdinaryConditionMetric::Territory(RuntimeTerritoryMetric::TrackPiecesTransition),
|
||||||
|
},
|
||||||
|
RealOrdinaryConditionMetadata {
|
||||||
|
raw_condition_id: 2317,
|
||||||
|
label: "Territory Electric Track Pieces",
|
||||||
|
metric: RealOrdinaryConditionMetric::Territory(RuntimeTerritoryMetric::TrackPiecesElectric),
|
||||||
|
},
|
||||||
|
RealOrdinaryConditionMetadata {
|
||||||
|
raw_condition_id: 2318,
|
||||||
|
label: "Territory Non-Electric Track Pieces",
|
||||||
|
metric: RealOrdinaryConditionMetric::Territory(RuntimeTerritoryMetric::TrackPiecesNonElectric),
|
||||||
|
},
|
||||||
|
RealOrdinaryConditionMetadata {
|
||||||
|
raw_condition_id: 2323,
|
||||||
|
label: "Company-Territory Track Pieces",
|
||||||
|
metric: RealOrdinaryConditionMetric::CompanyTerritory(RuntimeTrackMetric::Total),
|
||||||
|
},
|
||||||
|
RealOrdinaryConditionMetadata {
|
||||||
|
raw_condition_id: 2324,
|
||||||
|
label: "Company-Territory Single Track Pieces",
|
||||||
|
metric: RealOrdinaryConditionMetric::CompanyTerritory(RuntimeTrackMetric::Single),
|
||||||
|
},
|
||||||
|
RealOrdinaryConditionMetadata {
|
||||||
|
raw_condition_id: 2325,
|
||||||
|
label: "Company-Territory Double Track Pieces",
|
||||||
|
metric: RealOrdinaryConditionMetric::CompanyTerritory(RuntimeTrackMetric::Double),
|
||||||
|
},
|
||||||
|
RealOrdinaryConditionMetadata {
|
||||||
|
raw_condition_id: 2326,
|
||||||
|
label: "Company-Territory Transition Track Pieces",
|
||||||
|
metric: RealOrdinaryConditionMetric::CompanyTerritory(RuntimeTrackMetric::Transition),
|
||||||
|
},
|
||||||
|
RealOrdinaryConditionMetadata {
|
||||||
|
raw_condition_id: 2327,
|
||||||
|
label: "Company-Territory Electric Track Pieces",
|
||||||
|
metric: RealOrdinaryConditionMetric::CompanyTerritory(RuntimeTrackMetric::Electric),
|
||||||
|
},
|
||||||
|
RealOrdinaryConditionMetadata {
|
||||||
|
raw_condition_id: 2328,
|
||||||
|
label: "Company-Territory Non-Electric Track Pieces",
|
||||||
|
metric: RealOrdinaryConditionMetric::CompanyTerritory(RuntimeTrackMetric::NonElectric),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
struct KnownSpecialConditionDefinition {
|
struct KnownSpecialConditionDefinition {
|
||||||
slot_index: u8,
|
slot_index: u8,
|
||||||
|
|
@ -1321,6 +1449,8 @@ pub struct SmpLoadedPackedEventRecordSummary {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub grouped_effect_rows: Vec<SmpLoadedPackedEventGroupedEffectRowSummary>,
|
pub grouped_effect_rows: Vec<SmpLoadedPackedEventGroupedEffectRowSummary>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub decoded_conditions: Vec<RuntimeCondition>,
|
||||||
|
#[serde(default)]
|
||||||
pub decoded_actions: Vec<RuntimeEffect>,
|
pub decoded_actions: Vec<RuntimeEffect>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub executable_import_ready: bool,
|
pub executable_import_ready: bool,
|
||||||
|
|
@ -1369,6 +1499,16 @@ pub struct SmpLoadedPackedEventConditionRowSummary {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub candidate_name: Option<String>,
|
pub candidate_name: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub comparator: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub metric: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub semantic_family: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub semantic_preview: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub requires_candidate_name_binding: bool,
|
||||||
|
#[serde(default)]
|
||||||
pub notes: Vec<String>,
|
pub notes: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1853,6 +1993,7 @@ fn parse_synthetic_event_runtime_record_summary(
|
||||||
negative_sentinel_scope: None,
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts,
|
grouped_effect_row_counts,
|
||||||
grouped_effect_rows: Vec::new(),
|
grouped_effect_rows: Vec::new(),
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
decoded_actions,
|
decoded_actions,
|
||||||
executable_import_ready,
|
executable_import_ready,
|
||||||
notes: vec!["decoded from the current synthetic packed-event record harness".to_string()],
|
notes: vec!["decoded from the current synthetic packed-event record harness".to_string()],
|
||||||
|
|
@ -1958,15 +2099,27 @@ fn parse_real_event_runtime_record_summary(
|
||||||
let negative_sentinel_scope = compact_control.as_ref().and_then(|control| {
|
let negative_sentinel_scope = compact_control.as_ref().and_then(|control| {
|
||||||
derive_negative_sentinel_scope_summary(&standalone_condition_rows, control)
|
derive_negative_sentinel_scope_summary(&standalone_condition_rows, control)
|
||||||
});
|
});
|
||||||
|
let decoded_conditions = decode_real_condition_rows(
|
||||||
|
&standalone_condition_rows,
|
||||||
|
negative_sentinel_scope.as_ref(),
|
||||||
|
);
|
||||||
let decoded_actions = compact_control
|
let decoded_actions = compact_control
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|control| decode_real_grouped_effect_actions(&grouped_effect_rows, control))
|
.map(|control| decode_real_grouped_effect_actions(&grouped_effect_rows, control))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
let ordinary_condition_row_count = standalone_condition_rows
|
||||||
|
.iter()
|
||||||
|
.filter(|row| row.raw_condition_id >= 0)
|
||||||
|
.count();
|
||||||
let executable_import_ready = !grouped_effect_rows.is_empty()
|
let executable_import_ready = !grouped_effect_rows.is_empty()
|
||||||
&& decoded_actions.len() == grouped_effect_rows.len()
|
&& decoded_actions.len() == grouped_effect_rows.len()
|
||||||
|
&& decoded_conditions.len() == ordinary_condition_row_count
|
||||||
&& decoded_actions
|
&& decoded_actions
|
||||||
.iter()
|
.iter()
|
||||||
.all(runtime_effect_supported_for_save_import);
|
.all(runtime_effect_supported_for_save_import)
|
||||||
|
&& decoded_conditions
|
||||||
|
.iter()
|
||||||
|
.all(runtime_condition_supported_for_save_import);
|
||||||
let consumed_len = cursor;
|
let consumed_len = cursor;
|
||||||
Some((
|
Some((
|
||||||
SmpLoadedPackedEventRecordSummary {
|
SmpLoadedPackedEventRecordSummary {
|
||||||
|
|
@ -1989,6 +2142,7 @@ fn parse_real_event_runtime_record_summary(
|
||||||
negative_sentinel_scope,
|
negative_sentinel_scope,
|
||||||
grouped_effect_row_counts,
|
grouped_effect_row_counts,
|
||||||
grouped_effect_rows,
|
grouped_effect_rows,
|
||||||
|
decoded_conditions,
|
||||||
decoded_actions,
|
decoded_actions,
|
||||||
executable_import_ready,
|
executable_import_ready,
|
||||||
notes: vec![
|
notes: vec![
|
||||||
|
|
@ -2074,6 +2228,22 @@ fn parse_real_condition_row_summary(
|
||||||
) -> Option<SmpLoadedPackedEventConditionRowSummary> {
|
) -> Option<SmpLoadedPackedEventConditionRowSummary> {
|
||||||
let raw_condition_id = read_u32_at(row_bytes, 0)? as i32;
|
let raw_condition_id = read_u32_at(row_bytes, 0)? as i32;
|
||||||
let subtype = read_u8_at(row_bytes, 4)?;
|
let subtype = read_u8_at(row_bytes, 4)?;
|
||||||
|
let flag_bytes = row_bytes
|
||||||
|
.get(5..PACKED_EVENT_REAL_CONDITION_ROW_LEN)?
|
||||||
|
.to_vec();
|
||||||
|
let ordinary_metadata = real_ordinary_condition_metadata(raw_condition_id);
|
||||||
|
let comparator = ordinary_metadata
|
||||||
|
.and_then(|_| decode_real_condition_comparator(subtype))
|
||||||
|
.map(condition_comparator_label);
|
||||||
|
let metric = ordinary_metadata.map(|metadata| metadata.label.to_string());
|
||||||
|
let threshold = ordinary_metadata.and_then(|_| decode_real_condition_threshold(&flag_bytes));
|
||||||
|
let requires_candidate_name_binding = ordinary_metadata.is_some_and(|metadata| {
|
||||||
|
matches!(
|
||||||
|
metadata.metric,
|
||||||
|
RealOrdinaryConditionMetric::Territory(_)
|
||||||
|
| RealOrdinaryConditionMetric::CompanyTerritory(_)
|
||||||
|
) && candidate_name.is_some()
|
||||||
|
});
|
||||||
let mut notes = Vec::new();
|
let mut notes = Vec::new();
|
||||||
if raw_condition_id < 0 {
|
if raw_condition_id < 0 {
|
||||||
notes.push("negative sentinel-style condition row id".to_string());
|
notes.push("negative sentinel-style condition row id".to_string());
|
||||||
|
|
@ -2081,14 +2251,27 @@ fn parse_real_condition_row_summary(
|
||||||
if candidate_name.is_some() {
|
if candidate_name.is_some() {
|
||||||
notes.push("condition row carries candidate-name side string".to_string());
|
notes.push("condition row carries candidate-name side string".to_string());
|
||||||
}
|
}
|
||||||
|
if ordinary_metadata.is_none() && raw_condition_id >= 0 {
|
||||||
|
notes.push("ordinary condition id is not yet recovered in the checked-in condition table".to_string());
|
||||||
|
}
|
||||||
Some(SmpLoadedPackedEventConditionRowSummary {
|
Some(SmpLoadedPackedEventConditionRowSummary {
|
||||||
row_index,
|
row_index,
|
||||||
raw_condition_id,
|
raw_condition_id,
|
||||||
subtype,
|
subtype,
|
||||||
flag_bytes: row_bytes
|
flag_bytes,
|
||||||
.get(5..PACKED_EVENT_REAL_CONDITION_ROW_LEN)?
|
|
||||||
.to_vec(),
|
|
||||||
candidate_name,
|
candidate_name,
|
||||||
|
comparator,
|
||||||
|
metric,
|
||||||
|
semantic_family: ordinary_metadata.map(|_| "numeric_threshold".to_string()),
|
||||||
|
semantic_preview: ordinary_metadata.and_then(|metadata| {
|
||||||
|
threshold.map(|value| {
|
||||||
|
let comparator_text = decode_real_condition_comparator(subtype)
|
||||||
|
.map(condition_comparator_symbol)
|
||||||
|
.unwrap_or("?");
|
||||||
|
format!("Test {} {} {}", metadata.label, comparator_text, value)
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
requires_candidate_name_binding,
|
||||||
notes,
|
notes,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -2136,6 +2319,56 @@ fn decode_player_condition_test_scope(value: u8) -> Option<RuntimePlayerConditio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn real_ordinary_condition_metadata(
|
||||||
|
raw_condition_id: i32,
|
||||||
|
) -> Option<RealOrdinaryConditionMetadata> {
|
||||||
|
REAL_ORDINARY_CONDITION_METADATA
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.find(|metadata| metadata.raw_condition_id == raw_condition_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_real_condition_comparator(subtype: u8) -> Option<RuntimeConditionComparator> {
|
||||||
|
match subtype {
|
||||||
|
0 => Some(RuntimeConditionComparator::Ge),
|
||||||
|
1 => Some(RuntimeConditionComparator::Le),
|
||||||
|
2 => Some(RuntimeConditionComparator::Gt),
|
||||||
|
3 => Some(RuntimeConditionComparator::Lt),
|
||||||
|
4 => Some(RuntimeConditionComparator::Eq),
|
||||||
|
5 => Some(RuntimeConditionComparator::Ne),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_real_condition_threshold(flag_bytes: &[u8]) -> Option<i64> {
|
||||||
|
let raw = flag_bytes.get(0..4)?;
|
||||||
|
let mut bytes = [0u8; 4];
|
||||||
|
bytes.copy_from_slice(raw);
|
||||||
|
Some(i32::from_le_bytes(bytes).into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn condition_comparator_label(comparator: RuntimeConditionComparator) -> String {
|
||||||
|
match comparator {
|
||||||
|
RuntimeConditionComparator::Ge => "ge".to_string(),
|
||||||
|
RuntimeConditionComparator::Le => "le".to_string(),
|
||||||
|
RuntimeConditionComparator::Gt => "gt".to_string(),
|
||||||
|
RuntimeConditionComparator::Lt => "lt".to_string(),
|
||||||
|
RuntimeConditionComparator::Eq => "eq".to_string(),
|
||||||
|
RuntimeConditionComparator::Ne => "ne".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn condition_comparator_symbol(comparator: RuntimeConditionComparator) -> &'static str {
|
||||||
|
match comparator {
|
||||||
|
RuntimeConditionComparator::Ge => ">=",
|
||||||
|
RuntimeConditionComparator::Le => "<=",
|
||||||
|
RuntimeConditionComparator::Gt => ">",
|
||||||
|
RuntimeConditionComparator::Lt => "<",
|
||||||
|
RuntimeConditionComparator::Eq => "==",
|
||||||
|
RuntimeConditionComparator::Ne => "!=",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_real_grouped_effect_row_summary(
|
fn parse_real_grouped_effect_row_summary(
|
||||||
row_bytes: &[u8],
|
row_bytes: &[u8],
|
||||||
group_index: usize,
|
group_index: usize,
|
||||||
|
|
@ -2210,6 +2443,52 @@ fn parse_real_grouped_effect_row_summary(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn decode_real_condition_rows(
|
||||||
|
rows: &[SmpLoadedPackedEventConditionRowSummary],
|
||||||
|
negative_sentinel_scope: Option<&SmpLoadedPackedEventNegativeSentinelScopeSummary>,
|
||||||
|
) -> Vec<RuntimeCondition> {
|
||||||
|
rows.iter()
|
||||||
|
.filter(|row| row.raw_condition_id >= 0)
|
||||||
|
.filter_map(|row| decode_real_condition_row(row, negative_sentinel_scope))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_real_condition_row(
|
||||||
|
row: &SmpLoadedPackedEventConditionRowSummary,
|
||||||
|
negative_sentinel_scope: Option<&SmpLoadedPackedEventNegativeSentinelScopeSummary>,
|
||||||
|
) -> Option<RuntimeCondition> {
|
||||||
|
let metadata = real_ordinary_condition_metadata(row.raw_condition_id)?;
|
||||||
|
let comparator = decode_real_condition_comparator(row.subtype)?;
|
||||||
|
let value = decode_real_condition_threshold(&row.flag_bytes)?;
|
||||||
|
match metadata.metric {
|
||||||
|
RealOrdinaryConditionMetric::Company(metric) => Some(RuntimeCondition::CompanyNumericThreshold {
|
||||||
|
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||||
|
metric,
|
||||||
|
comparator,
|
||||||
|
value,
|
||||||
|
}),
|
||||||
|
RealOrdinaryConditionMetric::Territory(metric) => {
|
||||||
|
negative_sentinel_scope
|
||||||
|
.filter(|scope| scope.territory_scope_selector_is_0x63)
|
||||||
|
.map(|_| RuntimeCondition::TerritoryNumericThreshold {
|
||||||
|
metric,
|
||||||
|
comparator,
|
||||||
|
value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
RealOrdinaryConditionMetric::CompanyTerritory(metric) => {
|
||||||
|
negative_sentinel_scope
|
||||||
|
.filter(|scope| scope.territory_scope_selector_is_0x63)
|
||||||
|
.map(|_| RuntimeCondition::CompanyTerritoryNumericThreshold {
|
||||||
|
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||||
|
metric,
|
||||||
|
comparator,
|
||||||
|
value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn real_grouped_effect_descriptor_metadata(
|
fn real_grouped_effect_descriptor_metadata(
|
||||||
descriptor_id: u32,
|
descriptor_id: u32,
|
||||||
) -> Option<RealGroupedEffectDescriptorMetadata> {
|
) -> Option<RealGroupedEffectDescriptorMetadata> {
|
||||||
|
|
@ -2446,6 +2725,7 @@ fn parse_synthetic_event_runtime_record_template(
|
||||||
active: flags & 0x01 != 0,
|
active: flags & 0x01 != 0,
|
||||||
marks_collection_dirty: flags & 0x02 != 0,
|
marks_collection_dirty: flags & 0x02 != 0,
|
||||||
one_shot: flags & 0x04 != 0,
|
one_shot: flags & 0x04 != 0,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects,
|
effects,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -2522,6 +2802,14 @@ fn runtime_effect_supported_for_save_import(effect: &RuntimeEffect) -> bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn runtime_condition_supported_for_save_import(condition: &RuntimeCondition) -> bool {
|
||||||
|
match condition {
|
||||||
|
RuntimeCondition::CompanyNumericThreshold { .. }
|
||||||
|
| RuntimeCondition::TerritoryNumericThreshold { .. }
|
||||||
|
| RuntimeCondition::CompanyTerritoryNumericThreshold { .. } => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn build_unsupported_event_runtime_record_summaries(
|
fn build_unsupported_event_runtime_record_summaries(
|
||||||
live_entry_ids: &[u32],
|
live_entry_ids: &[u32],
|
||||||
note: &str,
|
note: &str,
|
||||||
|
|
@ -2549,6 +2837,7 @@ fn build_unsupported_event_runtime_record_summaries(
|
||||||
negative_sentinel_scope: None,
|
negative_sentinel_scope: None,
|
||||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||||
grouped_effect_rows: Vec::new(),
|
grouped_effect_rows: Vec::new(),
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
decoded_actions: Vec::new(),
|
decoded_actions: Vec::new(),
|
||||||
executable_import_ready: false,
|
executable_import_ready: false,
|
||||||
notes: vec![note.to_string()],
|
notes: vec![note.to_string()],
|
||||||
|
|
@ -7830,6 +8119,11 @@ mod tests {
|
||||||
subtype: 4,
|
subtype: 4,
|
||||||
flag_bytes: vec![0x30; 25],
|
flag_bytes: vec![0x30; 25],
|
||||||
candidate_name: Some("AutoPlant".to_string()),
|
candidate_name: Some("AutoPlant".to_string()),
|
||||||
|
comparator: None,
|
||||||
|
metric: None,
|
||||||
|
semantic_family: None,
|
||||||
|
semantic_preview: None,
|
||||||
|
requires_candidate_name_binding: false,
|
||||||
notes: vec![],
|
notes: vec![],
|
||||||
}];
|
}];
|
||||||
let summary = derive_negative_sentinel_scope_summary(
|
let summary = derive_negative_sentinel_scope_summary(
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,10 @@ use std::collections::BTreeSet;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
RuntimeCompanyControllerKind, RuntimeCompanyTarget, RuntimeEffect, RuntimeEventRecordTemplate,
|
RuntimeCompanyControllerKind, RuntimeCompanyMetric, RuntimeCompanyTarget, RuntimeCondition,
|
||||||
RuntimeState, RuntimeSummary, calendar::BoundaryEventKind,
|
RuntimeConditionComparator, RuntimeEffect, RuntimeEventRecordTemplate, RuntimeState,
|
||||||
|
RuntimeSummary, RuntimeTerritoryMetric, RuntimeTrackMetric, RuntimeTrackPieceCounts,
|
||||||
|
calendar::BoundaryEventKind,
|
||||||
};
|
};
|
||||||
|
|
||||||
const PERIODIC_TRIGGER_KIND_ORDER: [u8; 6] = [1, 0, 3, 2, 5, 4];
|
const PERIODIC_TRIGGER_KIND_ORDER: [u8; 6] = [1, 0, 3, 2, 5, 4];
|
||||||
|
|
@ -79,6 +81,11 @@ struct AppliedEffectsSummary {
|
||||||
removed_record_ids: Vec<u32>,
|
removed_record_ids: Vec<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct ResolvedConditionContext {
|
||||||
|
matching_company_ids: BTreeSet<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn execute_step_command(
|
pub fn execute_step_command(
|
||||||
state: &mut RuntimeState,
|
state: &mut RuntimeState,
|
||||||
command: &StepCommand,
|
command: &StepCommand,
|
||||||
|
|
@ -212,19 +219,31 @@ fn service_trigger_kind(
|
||||||
.or_insert(0) += 1;
|
.or_insert(0) += 1;
|
||||||
|
|
||||||
for index in eligible_indices {
|
for index in eligible_indices {
|
||||||
let (record_id, record_effects, record_marks_collection_dirty, record_one_shot) = {
|
let (
|
||||||
|
record_id,
|
||||||
|
record_conditions,
|
||||||
|
record_effects,
|
||||||
|
record_marks_collection_dirty,
|
||||||
|
record_one_shot,
|
||||||
|
) = {
|
||||||
let record = &state.event_runtime_records[index];
|
let record = &state.event_runtime_records[index];
|
||||||
(
|
(
|
||||||
record.record_id,
|
record.record_id,
|
||||||
|
record.conditions.clone(),
|
||||||
record.effects.clone(),
|
record.effects.clone(),
|
||||||
record.marks_collection_dirty,
|
record.marks_collection_dirty,
|
||||||
record.one_shot,
|
record.one_shot,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let Some(condition_context) = evaluate_record_conditions(state, &record_conditions)? else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
let effect_summary = apply_runtime_effects(
|
let effect_summary = apply_runtime_effects(
|
||||||
state,
|
state,
|
||||||
&record_effects,
|
&record_effects,
|
||||||
|
&condition_context,
|
||||||
&mut mutated_company_ids,
|
&mut mutated_company_ids,
|
||||||
&mut staged_event_graph_mutations,
|
&mut staged_event_graph_mutations,
|
||||||
)?;
|
)?;
|
||||||
|
|
@ -275,6 +294,7 @@ fn service_trigger_kind(
|
||||||
fn apply_runtime_effects(
|
fn apply_runtime_effects(
|
||||||
state: &mut RuntimeState,
|
state: &mut RuntimeState,
|
||||||
effects: &[RuntimeEffect],
|
effects: &[RuntimeEffect],
|
||||||
|
condition_context: &ResolvedConditionContext,
|
||||||
mutated_company_ids: &mut BTreeSet<u32>,
|
mutated_company_ids: &mut BTreeSet<u32>,
|
||||||
staged_event_graph_mutations: &mut Vec<EventGraphMutation>,
|
staged_event_graph_mutations: &mut Vec<EventGraphMutation>,
|
||||||
) -> Result<AppliedEffectsSummary, String> {
|
) -> Result<AppliedEffectsSummary, String> {
|
||||||
|
|
@ -286,7 +306,7 @@ fn apply_runtime_effects(
|
||||||
state.world_flags.insert(key.clone(), *value);
|
state.world_flags.insert(key.clone(), *value);
|
||||||
}
|
}
|
||||||
RuntimeEffect::SetCompanyCash { target, value } => {
|
RuntimeEffect::SetCompanyCash { target, value } => {
|
||||||
let company_ids = resolve_company_target_ids(state, target)?;
|
let company_ids = resolve_company_target_ids(state, target, condition_context)?;
|
||||||
for company_id in company_ids {
|
for company_id in company_ids {
|
||||||
let company = state
|
let company = state
|
||||||
.companies
|
.companies
|
||||||
|
|
@ -300,7 +320,7 @@ fn apply_runtime_effects(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RuntimeEffect::DeactivateCompany { target } => {
|
RuntimeEffect::DeactivateCompany { target } => {
|
||||||
let company_ids = resolve_company_target_ids(state, target)?;
|
let company_ids = resolve_company_target_ids(state, target, condition_context)?;
|
||||||
for company_id in company_ids {
|
for company_id in company_ids {
|
||||||
let company = state
|
let company = state
|
||||||
.companies
|
.companies
|
||||||
|
|
@ -319,7 +339,7 @@ fn apply_runtime_effects(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RuntimeEffect::SetCompanyTrackLayingCapacity { target, value } => {
|
RuntimeEffect::SetCompanyTrackLayingCapacity { target, value } => {
|
||||||
let company_ids = resolve_company_target_ids(state, target)?;
|
let company_ids = resolve_company_target_ids(state, target, condition_context)?;
|
||||||
for company_id in company_ids {
|
for company_id in company_ids {
|
||||||
let company = state
|
let company = state
|
||||||
.companies
|
.companies
|
||||||
|
|
@ -335,7 +355,7 @@ fn apply_runtime_effects(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RuntimeEffect::AdjustCompanyCash { target, delta } => {
|
RuntimeEffect::AdjustCompanyCash { target, delta } => {
|
||||||
let company_ids = resolve_company_target_ids(state, target)?;
|
let company_ids = resolve_company_target_ids(state, target, condition_context)?;
|
||||||
for company_id in company_ids {
|
for company_id in company_ids {
|
||||||
let company = state
|
let company = state
|
||||||
.companies
|
.companies
|
||||||
|
|
@ -352,7 +372,7 @@ fn apply_runtime_effects(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RuntimeEffect::AdjustCompanyDebt { target, delta } => {
|
RuntimeEffect::AdjustCompanyDebt { target, delta } => {
|
||||||
let company_ids = resolve_company_target_ids(state, target)?;
|
let company_ids = resolve_company_target_ids(state, target, condition_context)?;
|
||||||
for company_id in company_ids {
|
for company_id in company_ids {
|
||||||
let company = state
|
let company = state
|
||||||
.companies
|
.companies
|
||||||
|
|
@ -456,9 +476,114 @@ fn commit_staged_event_graph_mutations(
|
||||||
state.validate()
|
state.validate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn evaluate_record_conditions(
|
||||||
|
state: &RuntimeState,
|
||||||
|
conditions: &[RuntimeCondition],
|
||||||
|
) -> Result<Option<ResolvedConditionContext>, String> {
|
||||||
|
if conditions.is_empty() {
|
||||||
|
return Ok(Some(ResolvedConditionContext::default()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut company_matches: Option<BTreeSet<u32>> = None;
|
||||||
|
|
||||||
|
for condition in conditions {
|
||||||
|
match condition {
|
||||||
|
RuntimeCondition::CompanyNumericThreshold {
|
||||||
|
target,
|
||||||
|
metric,
|
||||||
|
comparator,
|
||||||
|
value,
|
||||||
|
} => {
|
||||||
|
let resolved = resolve_company_target_ids(
|
||||||
|
state,
|
||||||
|
target,
|
||||||
|
&ResolvedConditionContext::default(),
|
||||||
|
)?;
|
||||||
|
let matching = resolved
|
||||||
|
.into_iter()
|
||||||
|
.filter(|company_id| {
|
||||||
|
state.companies.iter().find(|company| company.company_id == *company_id).is_some_and(
|
||||||
|
|company| compare_condition_value(
|
||||||
|
company_metric_value(company, *metric),
|
||||||
|
*comparator,
|
||||||
|
*value,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<BTreeSet<_>>();
|
||||||
|
if matching.is_empty() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
intersect_company_matches(&mut company_matches, matching);
|
||||||
|
if company_matches.as_ref().is_some_and(BTreeSet::is_empty) {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RuntimeCondition::TerritoryNumericThreshold {
|
||||||
|
metric,
|
||||||
|
comparator,
|
||||||
|
value,
|
||||||
|
} => {
|
||||||
|
let actual = territory_metric_value(state, *metric);
|
||||||
|
if !compare_condition_value(actual, *comparator, *value) {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RuntimeCondition::CompanyTerritoryNumericThreshold {
|
||||||
|
target,
|
||||||
|
metric,
|
||||||
|
comparator,
|
||||||
|
value,
|
||||||
|
} => {
|
||||||
|
let resolved = resolve_company_target_ids(
|
||||||
|
state,
|
||||||
|
target,
|
||||||
|
&ResolvedConditionContext::default(),
|
||||||
|
)?;
|
||||||
|
let matching = resolved
|
||||||
|
.into_iter()
|
||||||
|
.filter(|company_id| {
|
||||||
|
compare_condition_value(
|
||||||
|
company_territory_metric_value(state, *company_id, *metric),
|
||||||
|
*comparator,
|
||||||
|
*value,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<BTreeSet<_>>();
|
||||||
|
if matching.is_empty() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
intersect_company_matches(&mut company_matches, matching);
|
||||||
|
if company_matches.as_ref().is_some_and(BTreeSet::is_empty) {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(ResolvedConditionContext {
|
||||||
|
matching_company_ids: company_matches.unwrap_or_default(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn intersect_company_matches(
|
||||||
|
company_matches: &mut Option<BTreeSet<u32>>,
|
||||||
|
next: BTreeSet<u32>,
|
||||||
|
) {
|
||||||
|
match company_matches {
|
||||||
|
Some(existing) => {
|
||||||
|
existing.retain(|company_id| next.contains(company_id));
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
*company_matches = Some(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn resolve_company_target_ids(
|
fn resolve_company_target_ids(
|
||||||
state: &RuntimeState,
|
state: &RuntimeState,
|
||||||
target: &RuntimeCompanyTarget,
|
target: &RuntimeCompanyTarget,
|
||||||
|
condition_context: &ResolvedConditionContext,
|
||||||
) -> Result<Vec<u32>, String> {
|
) -> Result<Vec<u32>, String> {
|
||||||
match target {
|
match target {
|
||||||
RuntimeCompanyTarget::AllActive => Ok(state
|
RuntimeCompanyTarget::AllActive => Ok(state
|
||||||
|
|
@ -538,11 +663,101 @@ fn resolve_company_target_ids(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RuntimeCompanyTarget::ConditionTrueCompany => {
|
RuntimeCompanyTarget::ConditionTrueCompany => {
|
||||||
Err("target requires condition-evaluation context".to_string())
|
if condition_context.matching_company_ids.is_empty() {
|
||||||
|
Err("target requires condition-evaluation context".to_string())
|
||||||
|
} else {
|
||||||
|
Ok(condition_context
|
||||||
|
.matching_company_ids
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn company_metric_value(company: &crate::RuntimeCompany, metric: RuntimeCompanyMetric) -> i64 {
|
||||||
|
match metric {
|
||||||
|
RuntimeCompanyMetric::CurrentCash => company.current_cash,
|
||||||
|
RuntimeCompanyMetric::TotalDebt => company.debt as i64,
|
||||||
|
RuntimeCompanyMetric::CreditRating => company.credit_rating_score.unwrap_or(0),
|
||||||
|
RuntimeCompanyMetric::PrimeRate => company.prime_rate.unwrap_or(0),
|
||||||
|
RuntimeCompanyMetric::TrackPiecesTotal => i64::from(company.track_piece_counts.total),
|
||||||
|
RuntimeCompanyMetric::TrackPiecesSingle => i64::from(company.track_piece_counts.single),
|
||||||
|
RuntimeCompanyMetric::TrackPiecesDouble => i64::from(company.track_piece_counts.double),
|
||||||
|
RuntimeCompanyMetric::TrackPiecesTransition => {
|
||||||
|
i64::from(company.track_piece_counts.transition)
|
||||||
|
}
|
||||||
|
RuntimeCompanyMetric::TrackPiecesElectric => {
|
||||||
|
i64::from(company.track_piece_counts.electric)
|
||||||
|
}
|
||||||
|
RuntimeCompanyMetric::TrackPiecesNonElectric => {
|
||||||
|
i64::from(company.track_piece_counts.non_electric)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn territory_metric_value(state: &RuntimeState, metric: RuntimeTerritoryMetric) -> i64 {
|
||||||
|
state.territories
|
||||||
|
.iter()
|
||||||
|
.map(|territory| {
|
||||||
|
track_piece_metric_value(
|
||||||
|
territory.track_piece_counts,
|
||||||
|
territory_metric_to_track_metric(metric),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn company_territory_metric_value(
|
||||||
|
state: &RuntimeState,
|
||||||
|
company_id: u32,
|
||||||
|
metric: RuntimeTrackMetric,
|
||||||
|
) -> i64 {
|
||||||
|
state.company_territory_track_piece_counts
|
||||||
|
.iter()
|
||||||
|
.filter(|entry| entry.company_id == company_id)
|
||||||
|
.map(|entry| track_piece_metric_value(entry.track_piece_counts, metric))
|
||||||
|
.sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn track_piece_metric_value(counts: RuntimeTrackPieceCounts, metric: RuntimeTrackMetric) -> i64 {
|
||||||
|
match metric {
|
||||||
|
RuntimeTrackMetric::Total => i64::from(counts.total),
|
||||||
|
RuntimeTrackMetric::Single => i64::from(counts.single),
|
||||||
|
RuntimeTrackMetric::Double => i64::from(counts.double),
|
||||||
|
RuntimeTrackMetric::Transition => i64::from(counts.transition),
|
||||||
|
RuntimeTrackMetric::Electric => i64::from(counts.electric),
|
||||||
|
RuntimeTrackMetric::NonElectric => i64::from(counts.non_electric),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn territory_metric_to_track_metric(metric: RuntimeTerritoryMetric) -> RuntimeTrackMetric {
|
||||||
|
match metric {
|
||||||
|
RuntimeTerritoryMetric::TrackPiecesTotal => RuntimeTrackMetric::Total,
|
||||||
|
RuntimeTerritoryMetric::TrackPiecesSingle => RuntimeTrackMetric::Single,
|
||||||
|
RuntimeTerritoryMetric::TrackPiecesDouble => RuntimeTrackMetric::Double,
|
||||||
|
RuntimeTerritoryMetric::TrackPiecesTransition => RuntimeTrackMetric::Transition,
|
||||||
|
RuntimeTerritoryMetric::TrackPiecesElectric => RuntimeTrackMetric::Electric,
|
||||||
|
RuntimeTerritoryMetric::TrackPiecesNonElectric => RuntimeTrackMetric::NonElectric,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare_condition_value(
|
||||||
|
actual: i64,
|
||||||
|
comparator: RuntimeConditionComparator,
|
||||||
|
expected: i64,
|
||||||
|
) -> bool {
|
||||||
|
match comparator {
|
||||||
|
RuntimeConditionComparator::Ge => actual >= expected,
|
||||||
|
RuntimeConditionComparator::Le => actual <= expected,
|
||||||
|
RuntimeConditionComparator::Gt => actual > expected,
|
||||||
|
RuntimeConditionComparator::Lt => actual < expected,
|
||||||
|
RuntimeConditionComparator::Eq => actual == expected,
|
||||||
|
RuntimeConditionComparator::Ne => actual != expected,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn apply_u64_delta(current: u64, delta: i64, company_id: u32) -> Result<u64, String> {
|
fn apply_u64_delta(current: u64, delta: i64, company_id: u32) -> Result<u64, String> {
|
||||||
if delta >= 0 {
|
if delta >= 0 {
|
||||||
current
|
current
|
||||||
|
|
@ -583,10 +798,15 @@ mod tests {
|
||||||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||||
current_cash: 10,
|
current_cash: 10,
|
||||||
debt: 0,
|
debt: 0,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
active: true,
|
active: true,
|
||||||
available_track_laying_capacity: None,
|
available_track_laying_capacity: None,
|
||||||
}],
|
}],
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
|
territories: Vec::new(),
|
||||||
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
@ -647,6 +867,7 @@ mod tests {
|
||||||
marks_collection_dirty: true,
|
marks_collection_dirty: true,
|
||||||
one_shot: false,
|
one_shot: false,
|
||||||
has_fired: false,
|
has_fired: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects: vec![RuntimeEffect::SetWorldFlag {
|
effects: vec![RuntimeEffect::SetWorldFlag {
|
||||||
key: "runtime.effect_fired".to_string(),
|
key: "runtime.effect_fired".to_string(),
|
||||||
value: true,
|
value: true,
|
||||||
|
|
@ -660,6 +881,7 @@ mod tests {
|
||||||
marks_collection_dirty: false,
|
marks_collection_dirty: false,
|
||||||
one_shot: false,
|
one_shot: false,
|
||||||
has_fired: false,
|
has_fired: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects: vec![RuntimeEffect::AdjustCompanyCash {
|
effects: vec![RuntimeEffect::AdjustCompanyCash {
|
||||||
target: RuntimeCompanyTarget::AllActive,
|
target: RuntimeCompanyTarget::AllActive,
|
||||||
delta: 5,
|
delta: 5,
|
||||||
|
|
@ -673,6 +895,7 @@ mod tests {
|
||||||
marks_collection_dirty: false,
|
marks_collection_dirty: false,
|
||||||
one_shot: false,
|
one_shot: false,
|
||||||
has_fired: false,
|
has_fired: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects: vec![RuntimeEffect::SetSpecialCondition {
|
effects: vec![RuntimeEffect::SetSpecialCondition {
|
||||||
label: "Dirty rerun fired".to_string(),
|
label: "Dirty rerun fired".to_string(),
|
||||||
value: 1,
|
value: 1,
|
||||||
|
|
@ -747,6 +970,9 @@ mod tests {
|
||||||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||||
current_cash: 10,
|
current_cash: 10,
|
||||||
debt: 5,
|
debt: 5,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
active: true,
|
active: true,
|
||||||
available_track_laying_capacity: None,
|
available_track_laying_capacity: None,
|
||||||
},
|
},
|
||||||
|
|
@ -755,6 +981,9 @@ mod tests {
|
||||||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||||
current_cash: 20,
|
current_cash: 20,
|
||||||
debt: 8,
|
debt: 8,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
active: true,
|
active: true,
|
||||||
available_track_laying_capacity: None,
|
available_track_laying_capacity: None,
|
||||||
},
|
},
|
||||||
|
|
@ -767,6 +996,7 @@ mod tests {
|
||||||
marks_collection_dirty: false,
|
marks_collection_dirty: false,
|
||||||
one_shot: false,
|
one_shot: false,
|
||||||
has_fired: false,
|
has_fired: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects: vec![
|
effects: vec![
|
||||||
RuntimeEffect::AdjustCompanyCash {
|
RuntimeEffect::AdjustCompanyCash {
|
||||||
target: RuntimeCompanyTarget::Ids { ids: vec![2] },
|
target: RuntimeCompanyTarget::Ids { ids: vec![2] },
|
||||||
|
|
@ -803,6 +1033,9 @@ mod tests {
|
||||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||||
current_cash: 10,
|
current_cash: 10,
|
||||||
debt: 0,
|
debt: 0,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
active: true,
|
active: true,
|
||||||
available_track_laying_capacity: None,
|
available_track_laying_capacity: None,
|
||||||
},
|
},
|
||||||
|
|
@ -811,6 +1044,9 @@ mod tests {
|
||||||
controller_kind: RuntimeCompanyControllerKind::Ai,
|
controller_kind: RuntimeCompanyControllerKind::Ai,
|
||||||
current_cash: 20,
|
current_cash: 20,
|
||||||
debt: 2,
|
debt: 2,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
active: true,
|
active: true,
|
||||||
available_track_laying_capacity: None,
|
available_track_laying_capacity: None,
|
||||||
},
|
},
|
||||||
|
|
@ -825,6 +1061,7 @@ mod tests {
|
||||||
marks_collection_dirty: false,
|
marks_collection_dirty: false,
|
||||||
one_shot: false,
|
one_shot: false,
|
||||||
has_fired: false,
|
has_fired: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects: vec![RuntimeEffect::AdjustCompanyCash {
|
effects: vec![RuntimeEffect::AdjustCompanyCash {
|
||||||
target: RuntimeCompanyTarget::HumanCompanies,
|
target: RuntimeCompanyTarget::HumanCompanies,
|
||||||
delta: 5,
|
delta: 5,
|
||||||
|
|
@ -838,6 +1075,7 @@ mod tests {
|
||||||
marks_collection_dirty: false,
|
marks_collection_dirty: false,
|
||||||
one_shot: false,
|
one_shot: false,
|
||||||
has_fired: false,
|
has_fired: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects: vec![RuntimeEffect::AdjustCompanyDebt {
|
effects: vec![RuntimeEffect::AdjustCompanyDebt {
|
||||||
target: RuntimeCompanyTarget::AiCompanies,
|
target: RuntimeCompanyTarget::AiCompanies,
|
||||||
delta: 3,
|
delta: 3,
|
||||||
|
|
@ -851,6 +1089,7 @@ mod tests {
|
||||||
marks_collection_dirty: false,
|
marks_collection_dirty: false,
|
||||||
one_shot: false,
|
one_shot: false,
|
||||||
has_fired: false,
|
has_fired: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects: vec![RuntimeEffect::AdjustCompanyCash {
|
effects: vec![RuntimeEffect::AdjustCompanyCash {
|
||||||
target: RuntimeCompanyTarget::SelectedCompany,
|
target: RuntimeCompanyTarget::SelectedCompany,
|
||||||
delta: 7,
|
delta: 7,
|
||||||
|
|
@ -884,6 +1123,7 @@ mod tests {
|
||||||
marks_collection_dirty: false,
|
marks_collection_dirty: false,
|
||||||
one_shot: false,
|
one_shot: false,
|
||||||
has_fired: false,
|
has_fired: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects: vec![RuntimeEffect::AdjustCompanyCash {
|
effects: vec![RuntimeEffect::AdjustCompanyCash {
|
||||||
target: RuntimeCompanyTarget::SelectedCompany,
|
target: RuntimeCompanyTarget::SelectedCompany,
|
||||||
delta: 1,
|
delta: 1,
|
||||||
|
|
@ -912,6 +1152,7 @@ mod tests {
|
||||||
marks_collection_dirty: false,
|
marks_collection_dirty: false,
|
||||||
one_shot: false,
|
one_shot: false,
|
||||||
has_fired: false,
|
has_fired: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects: vec![RuntimeEffect::AdjustCompanyCash {
|
effects: vec![RuntimeEffect::AdjustCompanyCash {
|
||||||
target: RuntimeCompanyTarget::HumanCompanies,
|
target: RuntimeCompanyTarget::HumanCompanies,
|
||||||
delta: 1,
|
delta: 1,
|
||||||
|
|
@ -938,6 +1179,9 @@ mod tests {
|
||||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||||
current_cash: 10,
|
current_cash: 10,
|
||||||
debt: 1,
|
debt: 1,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
active: true,
|
active: true,
|
||||||
available_track_laying_capacity: None,
|
available_track_laying_capacity: None,
|
||||||
},
|
},
|
||||||
|
|
@ -946,6 +1190,9 @@ mod tests {
|
||||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||||
current_cash: 20,
|
current_cash: 20,
|
||||||
debt: 2,
|
debt: 2,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
active: false,
|
active: false,
|
||||||
available_track_laying_capacity: None,
|
available_track_laying_capacity: None,
|
||||||
},
|
},
|
||||||
|
|
@ -954,6 +1201,9 @@ mod tests {
|
||||||
controller_kind: RuntimeCompanyControllerKind::Ai,
|
controller_kind: RuntimeCompanyControllerKind::Ai,
|
||||||
current_cash: 30,
|
current_cash: 30,
|
||||||
debt: 3,
|
debt: 3,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
active: true,
|
active: true,
|
||||||
available_track_laying_capacity: None,
|
available_track_laying_capacity: None,
|
||||||
},
|
},
|
||||||
|
|
@ -967,6 +1217,7 @@ mod tests {
|
||||||
marks_collection_dirty: false,
|
marks_collection_dirty: false,
|
||||||
one_shot: false,
|
one_shot: false,
|
||||||
has_fired: false,
|
has_fired: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects: vec![RuntimeEffect::AdjustCompanyCash {
|
effects: vec![RuntimeEffect::AdjustCompanyCash {
|
||||||
target: RuntimeCompanyTarget::AllActive,
|
target: RuntimeCompanyTarget::AllActive,
|
||||||
delta: 5,
|
delta: 5,
|
||||||
|
|
@ -980,6 +1231,7 @@ mod tests {
|
||||||
marks_collection_dirty: false,
|
marks_collection_dirty: false,
|
||||||
one_shot: false,
|
one_shot: false,
|
||||||
has_fired: false,
|
has_fired: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects: vec![RuntimeEffect::AdjustCompanyDebt {
|
effects: vec![RuntimeEffect::AdjustCompanyDebt {
|
||||||
target: RuntimeCompanyTarget::HumanCompanies,
|
target: RuntimeCompanyTarget::HumanCompanies,
|
||||||
delta: 4,
|
delta: 4,
|
||||||
|
|
@ -993,6 +1245,7 @@ mod tests {
|
||||||
marks_collection_dirty: false,
|
marks_collection_dirty: false,
|
||||||
one_shot: false,
|
one_shot: false,
|
||||||
has_fired: false,
|
has_fired: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects: vec![RuntimeEffect::AdjustCompanyDebt {
|
effects: vec![RuntimeEffect::AdjustCompanyDebt {
|
||||||
target: RuntimeCompanyTarget::AiCompanies,
|
target: RuntimeCompanyTarget::AiCompanies,
|
||||||
delta: 6,
|
delta: 6,
|
||||||
|
|
@ -1024,6 +1277,9 @@ mod tests {
|
||||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||||
current_cash: 10,
|
current_cash: 10,
|
||||||
debt: 0,
|
debt: 0,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
active: true,
|
active: true,
|
||||||
available_track_laying_capacity: Some(8),
|
available_track_laying_capacity: Some(8),
|
||||||
}],
|
}],
|
||||||
|
|
@ -1036,6 +1292,7 @@ mod tests {
|
||||||
marks_collection_dirty: false,
|
marks_collection_dirty: false,
|
||||||
one_shot: false,
|
one_shot: false,
|
||||||
has_fired: false,
|
has_fired: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects: vec![RuntimeEffect::DeactivateCompany {
|
effects: vec![RuntimeEffect::DeactivateCompany {
|
||||||
target: RuntimeCompanyTarget::SelectedCompany,
|
target: RuntimeCompanyTarget::SelectedCompany,
|
||||||
}],
|
}],
|
||||||
|
|
@ -1063,6 +1320,9 @@ mod tests {
|
||||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||||
current_cash: 10,
|
current_cash: 10,
|
||||||
debt: 0,
|
debt: 0,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
active: true,
|
active: true,
|
||||||
available_track_laying_capacity: None,
|
available_track_laying_capacity: None,
|
||||||
},
|
},
|
||||||
|
|
@ -1071,6 +1331,9 @@ mod tests {
|
||||||
controller_kind: RuntimeCompanyControllerKind::Ai,
|
controller_kind: RuntimeCompanyControllerKind::Ai,
|
||||||
current_cash: 20,
|
current_cash: 20,
|
||||||
debt: 0,
|
debt: 0,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
active: true,
|
active: true,
|
||||||
available_track_laying_capacity: None,
|
available_track_laying_capacity: None,
|
||||||
},
|
},
|
||||||
|
|
@ -1083,6 +1346,7 @@ mod tests {
|
||||||
marks_collection_dirty: false,
|
marks_collection_dirty: false,
|
||||||
one_shot: false,
|
one_shot: false,
|
||||||
has_fired: false,
|
has_fired: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects: vec![RuntimeEffect::SetCompanyTrackLayingCapacity {
|
effects: vec![RuntimeEffect::SetCompanyTrackLayingCapacity {
|
||||||
target: RuntimeCompanyTarget::Ids { ids: vec![2] },
|
target: RuntimeCompanyTarget::Ids { ids: vec![2] },
|
||||||
value: Some(14),
|
value: Some(14),
|
||||||
|
|
@ -1113,6 +1377,7 @@ mod tests {
|
||||||
marks_collection_dirty: false,
|
marks_collection_dirty: false,
|
||||||
one_shot: false,
|
one_shot: false,
|
||||||
has_fired: false,
|
has_fired: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects: vec![RuntimeEffect::AdjustCompanyCash {
|
effects: vec![RuntimeEffect::AdjustCompanyCash {
|
||||||
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||||
delta: 1,
|
delta: 1,
|
||||||
|
|
@ -1141,6 +1406,7 @@ mod tests {
|
||||||
marks_collection_dirty: false,
|
marks_collection_dirty: false,
|
||||||
one_shot: true,
|
one_shot: true,
|
||||||
has_fired: false,
|
has_fired: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects: vec![RuntimeEffect::SetWorldFlag {
|
effects: vec![RuntimeEffect::SetWorldFlag {
|
||||||
key: "one_shot".to_string(),
|
key: "one_shot".to_string(),
|
||||||
value: true,
|
value: true,
|
||||||
|
|
@ -1177,6 +1443,9 @@ mod tests {
|
||||||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||||
current_cash: 10,
|
current_cash: 10,
|
||||||
debt: 2,
|
debt: 2,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
active: true,
|
active: true,
|
||||||
available_track_laying_capacity: None,
|
available_track_laying_capacity: None,
|
||||||
}],
|
}],
|
||||||
|
|
@ -1188,6 +1457,7 @@ mod tests {
|
||||||
marks_collection_dirty: false,
|
marks_collection_dirty: false,
|
||||||
one_shot: false,
|
one_shot: false,
|
||||||
has_fired: false,
|
has_fired: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects: vec![RuntimeEffect::AdjustCompanyDebt {
|
effects: vec![RuntimeEffect::AdjustCompanyDebt {
|
||||||
target: RuntimeCompanyTarget::AllActive,
|
target: RuntimeCompanyTarget::AllActive,
|
||||||
delta: -3,
|
delta: -3,
|
||||||
|
|
@ -1215,6 +1485,7 @@ mod tests {
|
||||||
marks_collection_dirty: false,
|
marks_collection_dirty: false,
|
||||||
one_shot: true,
|
one_shot: true,
|
||||||
has_fired: false,
|
has_fired: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects: vec![RuntimeEffect::AppendEventRecord {
|
effects: vec![RuntimeEffect::AppendEventRecord {
|
||||||
record: Box::new(RuntimeEventRecordTemplate {
|
record: Box::new(RuntimeEventRecordTemplate {
|
||||||
record_id: 41,
|
record_id: 41,
|
||||||
|
|
@ -1222,6 +1493,7 @@ mod tests {
|
||||||
active: true,
|
active: true,
|
||||||
marks_collection_dirty: false,
|
marks_collection_dirty: false,
|
||||||
one_shot: false,
|
one_shot: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects: vec![RuntimeEffect::SetWorldFlag {
|
effects: vec![RuntimeEffect::SetWorldFlag {
|
||||||
key: "follow_on_later_pass".to_string(),
|
key: "follow_on_later_pass".to_string(),
|
||||||
value: true,
|
value: true,
|
||||||
|
|
@ -1268,6 +1540,7 @@ mod tests {
|
||||||
marks_collection_dirty: true,
|
marks_collection_dirty: true,
|
||||||
one_shot: false,
|
one_shot: false,
|
||||||
has_fired: false,
|
has_fired: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects: vec![RuntimeEffect::AppendEventRecord {
|
effects: vec![RuntimeEffect::AppendEventRecord {
|
||||||
record: Box::new(RuntimeEventRecordTemplate {
|
record: Box::new(RuntimeEventRecordTemplate {
|
||||||
record_id: 51,
|
record_id: 51,
|
||||||
|
|
@ -1275,6 +1548,7 @@ mod tests {
|
||||||
active: true,
|
active: true,
|
||||||
marks_collection_dirty: false,
|
marks_collection_dirty: false,
|
||||||
one_shot: false,
|
one_shot: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects: vec![RuntimeEffect::SetWorldFlag {
|
effects: vec![RuntimeEffect::SetWorldFlag {
|
||||||
key: "dirty_rerun_follow_on".to_string(),
|
key: "dirty_rerun_follow_on".to_string(),
|
||||||
value: true,
|
value: true,
|
||||||
|
|
@ -1314,6 +1588,7 @@ mod tests {
|
||||||
marks_collection_dirty: false,
|
marks_collection_dirty: false,
|
||||||
one_shot: true,
|
one_shot: true,
|
||||||
has_fired: false,
|
has_fired: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects: vec![
|
effects: vec![
|
||||||
RuntimeEffect::AppendEventRecord {
|
RuntimeEffect::AppendEventRecord {
|
||||||
record: Box::new(RuntimeEventRecordTemplate {
|
record: Box::new(RuntimeEventRecordTemplate {
|
||||||
|
|
@ -1322,6 +1597,7 @@ mod tests {
|
||||||
active: true,
|
active: true,
|
||||||
marks_collection_dirty: false,
|
marks_collection_dirty: false,
|
||||||
one_shot: false,
|
one_shot: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects: vec![RuntimeEffect::SetCandidateAvailability {
|
effects: vec![RuntimeEffect::SetCandidateAvailability {
|
||||||
name: "Appended Industry".to_string(),
|
name: "Appended Industry".to_string(),
|
||||||
value: 1,
|
value: 1,
|
||||||
|
|
@ -1341,6 +1617,7 @@ mod tests {
|
||||||
marks_collection_dirty: false,
|
marks_collection_dirty: false,
|
||||||
one_shot: false,
|
one_shot: false,
|
||||||
has_fired: false,
|
has_fired: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects: vec![RuntimeEffect::SetWorldFlag {
|
effects: vec![RuntimeEffect::SetWorldFlag {
|
||||||
key: "deactivated_after_first_pass".to_string(),
|
key: "deactivated_after_first_pass".to_string(),
|
||||||
value: true,
|
value: true,
|
||||||
|
|
@ -1354,6 +1631,7 @@ mod tests {
|
||||||
marks_collection_dirty: false,
|
marks_collection_dirty: false,
|
||||||
one_shot: false,
|
one_shot: false,
|
||||||
has_fired: false,
|
has_fired: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects: vec![RuntimeEffect::SetSpecialCondition {
|
effects: vec![RuntimeEffect::SetSpecialCondition {
|
||||||
label: "Activated On Second Pass".to_string(),
|
label: "Activated On Second Pass".to_string(),
|
||||||
value: 1,
|
value: 1,
|
||||||
|
|
@ -1367,6 +1645,7 @@ mod tests {
|
||||||
marks_collection_dirty: false,
|
marks_collection_dirty: false,
|
||||||
one_shot: false,
|
one_shot: false,
|
||||||
has_fired: false,
|
has_fired: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects: vec![RuntimeEffect::SetWorldFlag {
|
effects: vec![RuntimeEffect::SetWorldFlag {
|
||||||
key: "removed_after_first_pass".to_string(),
|
key: "removed_after_first_pass".to_string(),
|
||||||
value: true,
|
value: true,
|
||||||
|
|
@ -1436,6 +1715,7 @@ mod tests {
|
||||||
marks_collection_dirty: false,
|
marks_collection_dirty: false,
|
||||||
one_shot: false,
|
one_shot: false,
|
||||||
has_fired: false,
|
has_fired: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects: vec![RuntimeEffect::AppendEventRecord {
|
effects: vec![RuntimeEffect::AppendEventRecord {
|
||||||
record: Box::new(RuntimeEventRecordTemplate {
|
record: Box::new(RuntimeEventRecordTemplate {
|
||||||
record_id: 71,
|
record_id: 71,
|
||||||
|
|
@ -1443,6 +1723,7 @@ mod tests {
|
||||||
active: true,
|
active: true,
|
||||||
marks_collection_dirty: false,
|
marks_collection_dirty: false,
|
||||||
one_shot: false,
|
one_shot: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects: Vec::new(),
|
effects: Vec::new(),
|
||||||
}),
|
}),
|
||||||
}],
|
}],
|
||||||
|
|
@ -1455,6 +1736,7 @@ mod tests {
|
||||||
marks_collection_dirty: false,
|
marks_collection_dirty: false,
|
||||||
one_shot: false,
|
one_shot: false,
|
||||||
has_fired: false,
|
has_fired: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects: Vec::new(),
|
effects: Vec::new(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
@ -1480,6 +1762,7 @@ mod tests {
|
||||||
marks_collection_dirty: false,
|
marks_collection_dirty: false,
|
||||||
one_shot: false,
|
one_shot: false,
|
||||||
has_fired: false,
|
has_fired: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
effects: vec![RuntimeEffect::ActivateEventRecord { record_id: 999 }],
|
effects: vec![RuntimeEffect::ActivateEventRecord { record_id: 999 }],
|
||||||
}],
|
}],
|
||||||
..state()
|
..state()
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,8 @@ pub struct RuntimeSummary {
|
||||||
pub metadata_count: usize,
|
pub metadata_count: usize,
|
||||||
pub company_count: usize,
|
pub company_count: usize,
|
||||||
pub active_company_count: usize,
|
pub active_company_count: usize,
|
||||||
|
pub territory_count: usize,
|
||||||
|
pub company_territory_track_count: usize,
|
||||||
pub packed_event_collection_present: bool,
|
pub packed_event_collection_present: bool,
|
||||||
pub packed_event_record_count: usize,
|
pub packed_event_record_count: usize,
|
||||||
pub packed_event_decoded_record_count: usize,
|
pub packed_event_decoded_record_count: usize,
|
||||||
|
|
@ -42,6 +44,9 @@ pub struct RuntimeSummary {
|
||||||
pub packed_event_blocked_company_condition_scope_disabled_count: usize,
|
pub packed_event_blocked_company_condition_scope_disabled_count: usize,
|
||||||
pub packed_event_blocked_player_condition_scope_count: usize,
|
pub packed_event_blocked_player_condition_scope_count: usize,
|
||||||
pub packed_event_blocked_territory_condition_scope_count: usize,
|
pub packed_event_blocked_territory_condition_scope_count: usize,
|
||||||
|
pub packed_event_blocked_missing_territory_context_count: usize,
|
||||||
|
pub packed_event_blocked_named_territory_binding_count: usize,
|
||||||
|
pub packed_event_blocked_unmapped_ordinary_condition_count: usize,
|
||||||
pub packed_event_blocked_missing_compact_control_count: usize,
|
pub packed_event_blocked_missing_compact_control_count: usize,
|
||||||
pub packed_event_blocked_unmapped_real_descriptor_count: usize,
|
pub packed_event_blocked_unmapped_real_descriptor_count: usize,
|
||||||
pub packed_event_blocked_structural_only_count: usize,
|
pub packed_event_blocked_structural_only_count: usize,
|
||||||
|
|
@ -131,6 +136,8 @@ impl RuntimeSummary {
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|company| company.active)
|
.filter(|company| company.active)
|
||||||
.count(),
|
.count(),
|
||||||
|
territory_count: state.territories.len(),
|
||||||
|
company_territory_track_count: state.company_territory_track_piece_counts.len(),
|
||||||
packed_event_collection_present: state.packed_event_collection.is_some(),
|
packed_event_collection_present: state.packed_event_collection.is_some(),
|
||||||
packed_event_record_count: state
|
packed_event_record_count: state
|
||||||
.packed_event_collection
|
.packed_event_collection
|
||||||
|
|
@ -267,6 +274,48 @@ impl RuntimeSummary {
|
||||||
.count()
|
.count()
|
||||||
})
|
})
|
||||||
.unwrap_or(0),
|
.unwrap_or(0),
|
||||||
|
packed_event_blocked_missing_territory_context_count: state
|
||||||
|
.packed_event_collection
|
||||||
|
.as_ref()
|
||||||
|
.map(|summary| {
|
||||||
|
summary
|
||||||
|
.records
|
||||||
|
.iter()
|
||||||
|
.filter(|record| {
|
||||||
|
record.import_outcome.as_deref()
|
||||||
|
== Some("blocked_missing_territory_context")
|
||||||
|
})
|
||||||
|
.count()
|
||||||
|
})
|
||||||
|
.unwrap_or(0),
|
||||||
|
packed_event_blocked_named_territory_binding_count: state
|
||||||
|
.packed_event_collection
|
||||||
|
.as_ref()
|
||||||
|
.map(|summary| {
|
||||||
|
summary
|
||||||
|
.records
|
||||||
|
.iter()
|
||||||
|
.filter(|record| {
|
||||||
|
record.import_outcome.as_deref()
|
||||||
|
== Some("blocked_named_territory_binding")
|
||||||
|
})
|
||||||
|
.count()
|
||||||
|
})
|
||||||
|
.unwrap_or(0),
|
||||||
|
packed_event_blocked_unmapped_ordinary_condition_count: state
|
||||||
|
.packed_event_collection
|
||||||
|
.as_ref()
|
||||||
|
.map(|summary| {
|
||||||
|
summary
|
||||||
|
.records
|
||||||
|
.iter()
|
||||||
|
.filter(|record| {
|
||||||
|
record.import_outcome.as_deref()
|
||||||
|
== Some("blocked_unmapped_ordinary_condition")
|
||||||
|
})
|
||||||
|
.count()
|
||||||
|
})
|
||||||
|
.unwrap_or(0),
|
||||||
packed_event_blocked_missing_compact_control_count: state
|
packed_event_blocked_missing_compact_control_count: state
|
||||||
.packed_event_collection
|
.packed_event_collection
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
@ -355,6 +404,7 @@ mod tests {
|
||||||
use crate::{
|
use crate::{
|
||||||
CalendarPoint, RuntimeCompany, RuntimeCompanyControllerKind,
|
CalendarPoint, RuntimeCompany, RuntimeCompanyControllerKind,
|
||||||
RuntimePackedEventCollectionSummary, RuntimePackedEventRecordSummary,
|
RuntimePackedEventCollectionSummary, RuntimePackedEventRecordSummary,
|
||||||
|
RuntimeTrackPieceCounts,
|
||||||
RuntimeSaveProfileState, RuntimeServiceState, RuntimeState, RuntimeWorldRestoreState,
|
RuntimeSaveProfileState, RuntimeServiceState, RuntimeState, RuntimeWorldRestoreState,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -375,6 +425,8 @@ mod tests {
|
||||||
metadata: BTreeMap::new(),
|
metadata: BTreeMap::new(),
|
||||||
companies: Vec::new(),
|
companies: Vec::new(),
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
|
territories: Vec::new(),
|
||||||
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
packed_event_collection: Some(RuntimePackedEventCollectionSummary {
|
packed_event_collection: Some(RuntimePackedEventCollectionSummary {
|
||||||
source_kind: "packed-event-runtime-collection".to_string(),
|
source_kind: "packed-event-runtime-collection".to_string(),
|
||||||
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
||||||
|
|
@ -407,6 +459,7 @@ mod tests {
|
||||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||||
grouped_effect_rows: Vec::new(),
|
grouped_effect_rows: Vec::new(),
|
||||||
grouped_company_targets: Vec::new(),
|
grouped_company_targets: Vec::new(),
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
decoded_actions: Vec::new(),
|
decoded_actions: Vec::new(),
|
||||||
executable_import_ready: false,
|
executable_import_ready: false,
|
||||||
import_outcome: Some("blocked_missing_compact_control".to_string()),
|
import_outcome: Some("blocked_missing_compact_control".to_string()),
|
||||||
|
|
@ -431,6 +484,7 @@ mod tests {
|
||||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||||
grouped_effect_rows: Vec::new(),
|
grouped_effect_rows: Vec::new(),
|
||||||
grouped_company_targets: Vec::new(),
|
grouped_company_targets: Vec::new(),
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
decoded_actions: Vec::new(),
|
decoded_actions: Vec::new(),
|
||||||
executable_import_ready: false,
|
executable_import_ready: false,
|
||||||
import_outcome: Some("blocked_missing_company_context".to_string()),
|
import_outcome: Some("blocked_missing_company_context".to_string()),
|
||||||
|
|
@ -455,6 +509,7 @@ mod tests {
|
||||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||||
grouped_effect_rows: Vec::new(),
|
grouped_effect_rows: Vec::new(),
|
||||||
grouped_company_targets: Vec::new(),
|
grouped_company_targets: Vec::new(),
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
decoded_actions: Vec::new(),
|
decoded_actions: Vec::new(),
|
||||||
executable_import_ready: false,
|
executable_import_ready: false,
|
||||||
import_outcome: Some(
|
import_outcome: Some(
|
||||||
|
|
@ -481,6 +536,7 @@ mod tests {
|
||||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||||
grouped_effect_rows: Vec::new(),
|
grouped_effect_rows: Vec::new(),
|
||||||
grouped_company_targets: Vec::new(),
|
grouped_company_targets: Vec::new(),
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
decoded_actions: Vec::new(),
|
decoded_actions: Vec::new(),
|
||||||
executable_import_ready: false,
|
executable_import_ready: false,
|
||||||
import_outcome: Some("blocked_player_condition_scope".to_string()),
|
import_outcome: Some("blocked_player_condition_scope".to_string()),
|
||||||
|
|
@ -505,6 +561,7 @@ mod tests {
|
||||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||||
grouped_effect_rows: Vec::new(),
|
grouped_effect_rows: Vec::new(),
|
||||||
grouped_company_targets: Vec::new(),
|
grouped_company_targets: Vec::new(),
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
decoded_actions: Vec::new(),
|
decoded_actions: Vec::new(),
|
||||||
executable_import_ready: false,
|
executable_import_ready: false,
|
||||||
import_outcome: Some("blocked_territory_condition_scope".to_string()),
|
import_outcome: Some("blocked_territory_condition_scope".to_string()),
|
||||||
|
|
@ -573,6 +630,9 @@ mod tests {
|
||||||
company_id: 1,
|
company_id: 1,
|
||||||
current_cash: 10,
|
current_cash: 10,
|
||||||
debt: 0,
|
debt: 0,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
active: true,
|
active: true,
|
||||||
available_track_laying_capacity: None,
|
available_track_laying_capacity: None,
|
||||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||||
|
|
@ -581,12 +641,17 @@ mod tests {
|
||||||
company_id: 2,
|
company_id: 2,
|
||||||
current_cash: 20,
|
current_cash: 20,
|
||||||
debt: 0,
|
debt: 0,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
active: false,
|
active: false,
|
||||||
available_track_laying_capacity: Some(7),
|
available_track_laying_capacity: Some(7),
|
||||||
controller_kind: RuntimeCompanyControllerKind::Ai,
|
controller_kind: RuntimeCompanyControllerKind::Ai,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
selected_company_id: None,
|
selected_company_id: None,
|
||||||
|
territories: Vec::new(),
|
||||||
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
|
||||||
|
|
@ -84,8 +84,11 @@ The highest-value next passes are now:
|
||||||
- widen real packed-event executable coverage descriptor by descriptor after identity, target mask,
|
- widen real packed-event executable coverage descriptor by descriptor after identity, target mask,
|
||||||
and normalized effect semantics are all grounded, not just after row framing is parsed
|
and normalized effect semantics are all grounded, not just after row framing is parsed
|
||||||
- the first grounded condition-side unlock now exists for negative-sentinel `raw_condition_id = -1`
|
- the first grounded condition-side unlock now exists for negative-sentinel `raw_condition_id = -1`
|
||||||
company scopes; broader ordinary condition-id evaluation and player/territory runtime ownership
|
company scopes, and the first ordinary nonnegative condition batch now executes too: numeric
|
||||||
are the remaining condition frontier, and mixed supported/unsupported real rows stay parity-only
|
thresholds for company finance, company track, aggregate territory track, and company-territory
|
||||||
|
track
|
||||||
|
- named-territory ordinary rows and player-owned condition scope are still the remaining condition
|
||||||
|
frontier, and mixed supported/unsupported real rows stay parity-only
|
||||||
- keep in mind that the current local `.gms` corpus still exports with no packed event collection,
|
- keep in mind that the current local `.gms` corpus still exports with no packed event collection,
|
||||||
so real descriptor mapping needs to stay plumbing-first until better captures exist
|
so real descriptor mapping needs to stay plumbing-first until better captures exist
|
||||||
- use `rrt-hook` primarily as optional capture or integration tooling, not as the first execution
|
- use `rrt-hook` primarily as optional capture or integration tooling, not as the first execution
|
||||||
|
|
|
||||||
|
|
@ -35,11 +35,15 @@ Implemented today:
|
||||||
`raw_condition_id = -1` company scope lowers `condition_true_company` into normalized company
|
`raw_condition_id = -1` company scope lowers `condition_true_company` into normalized company
|
||||||
targets during import, while player and territory scope variants remain parity-visible and
|
targets during import, while player and territory scope variants remain parity-visible and
|
||||||
explicitly blocked
|
explicitly blocked
|
||||||
|
- the first ordinary nonnegative condition-id batch now executes too: numeric-threshold company
|
||||||
|
finance, company track, aggregate territory track, and company-territory track rows can import
|
||||||
|
through overlay-backed runtime context, while named-territory bindings stay parity-only and
|
||||||
|
player-owned condition scope still has no runtime owner
|
||||||
|
|
||||||
That means the next implementation work is breadth, not bootstrap. The recommended next slice is
|
That means the next implementation work is breadth, not bootstrap. The recommended next slice is
|
||||||
ordinary nonnegative condition-id semantics plus runtime ownership for the still-blocked player and
|
broader ordinary condition-id coverage beyond numeric thresholds, plus runtime ownership for the
|
||||||
territory scope families, alongside broader real grouped-descriptor coverage beyond the current
|
still-blocked player-scoped and named-territory condition families, alongside wider real
|
||||||
company-scoped batch.
|
grouped-descriptor coverage beyond the current company-scoped batch.
|
||||||
|
|
||||||
## Why This Boundary
|
## Why This Boundary
|
||||||
|
|
||||||
|
|
@ -236,8 +240,10 @@ Current status:
|
||||||
raw `.smp` binaries
|
raw `.smp` binaries
|
||||||
- overlay-backed captured-runtime inputs now provide enough runtime company context for symbolic
|
- overlay-backed captured-runtime inputs now provide enough runtime company context for symbolic
|
||||||
selected-company and controller-role scopes without inventing company state from save bytes alone
|
selected-company and controller-role scopes without inventing company state from save bytes alone
|
||||||
- the remaining gap is wider real grouped-descriptor semantic coverage plus ordinary condition-id
|
- aggregate territory context and company-territory track counters now flow through tracked overlay
|
||||||
evaluation and player/territory runtime ownership, not first-pass captured-runtime plumbing
|
snapshots, so the remaining gap is broader ordinary condition-id coverage beyond numeric
|
||||||
|
thresholds, named-territory binding, player runtime ownership, and wider real grouped-descriptor
|
||||||
|
semantic coverage, not first-pass captured-runtime plumbing
|
||||||
|
|
||||||
### Milestone 4: Domain Expansion
|
### Milestone 4: Domain Expansion
|
||||||
|
|
||||||
|
|
@ -380,48 +386,52 @@ Checked-in fixture families already include:
|
||||||
|
|
||||||
## Next Slice
|
## Next Slice
|
||||||
|
|
||||||
The recommended next implementation slice is broader real grouped-descriptor coverage on top of the
|
The recommended next implementation slice is broader ordinary-condition breadth on top of the
|
||||||
now-stable compact-control, symbolic-target, and current company-scoped real-family batch.
|
now-stable numeric-threshold, overlay-context, and current company-scoped real-descriptor batch.
|
||||||
|
|
||||||
Target behavior:
|
Target behavior:
|
||||||
|
|
||||||
- keep descriptors `2` `Company Cash`, `13` `Deactivate Company`, and `16`
|
- preserve the current proof set for real ordinary-condition execution:
|
||||||
`Company Track Pieces Buildable` as the proof that real grouped rows can cross the whole path:
|
company finance, company track, aggregate territory track, and company-territory track numeric
|
||||||
parse, semantic summary, overlay-backed import, and ordinary trigger execution
|
thresholds all pass through parse, semantic summary, overlay-backed import, and ordinary trigger
|
||||||
- recover more real descriptor identities from the checked-in effect table and expose their target
|
execution
|
||||||
masks and conservative semantic previews without guessing unsupported behavior
|
- extend ordinary condition coverage beyond numeric thresholds only when comparator semantics,
|
||||||
- widen executable real import only when both descriptor identity and runtime effect semantics are
|
runtime ownership, and binding rules are grounded enough to lower honestly into the normalized
|
||||||
grounded enough to map into the normalized runtime path honestly
|
runtime path
|
||||||
- keep condition-relative company scopes explicit until a real condition evaluator exists, instead
|
- keep named-territory ordinary rows explicit and parity-visible until candidate-name territory
|
||||||
of silently degrading or inventing target resolution
|
binding is grounded
|
||||||
|
- keep player-owned condition scope explicit and parity-visible until there is a first-class player
|
||||||
|
runtime model
|
||||||
|
- continue widening real grouped-descriptor execution only when both descriptor identity and
|
||||||
|
runtime effect semantics are grounded enough to map into the normalized runtime path honestly
|
||||||
|
|
||||||
Public-model expectations for that slice:
|
Public-model expectations for that slice:
|
||||||
|
|
||||||
- additional checked-in grouped-descriptor metadata entries keyed by recovered descriptor id
|
- additional checked-in ordinary-condition metadata entries beyond the current numeric-threshold
|
||||||
- more parity summaries with real descriptor labels, target masks, parameter families, and semantic
|
allowlist
|
||||||
previews
|
- richer runtime ownership for still-blocked condition domains such as named territory and player
|
||||||
- more selective real-row `decoded_actions` only where the descriptor-to-runtime mapping is
|
scope
|
||||||
supported end to end
|
- more selective real-row `decoded_conditions` and `decoded_actions` only where the
|
||||||
|
condition/effect-to-runtime mapping is supported end to end
|
||||||
|
|
||||||
Fixture work for that slice:
|
Fixture work for that slice:
|
||||||
|
|
||||||
- preserve the parity-heavy tracked sample as the condition-relative blocked frontier while it now
|
- preserve the new ordinary-condition tracked overlays for executable company finance, company
|
||||||
carries recovered `Company Cash` semantics with executable import readiness
|
track, aggregate territory track, and company-territory track thresholds
|
||||||
- keep overlay-backed captured fixtures for the executable company-scoped real families:
|
- preserve the named-territory tracked overlay as the explicit binding blocker frontier
|
||||||
`Company Cash`, `Deactivate Company`, and `Company Track Pieces Buildable`
|
- keep the older negative-sentinel, mixed real-row, and company-scoped descriptor fixtures green so
|
||||||
- keep a mixed real-row overlay fixture to lock the all-or-nothing parity rule for partially
|
ordinary-condition breadth does not regress descriptor-side execution
|
||||||
supported real records
|
|
||||||
- keep synthetic harness, save-slice, and overlay paths green as the real descriptor surface widens
|
- keep synthetic harness, save-slice, and overlay paths green as the real descriptor surface widens
|
||||||
|
|
||||||
Current local constraint:
|
Current local constraint:
|
||||||
|
|
||||||
- the local checked-in and on-disk `.gms` corpus still does not provide a richer captured packed
|
- the local checked-in and on-disk `.gms` corpus still does not provide a richer captured packed
|
||||||
event save set, so descriptor recovery must continue to rely on the grounded static tables and
|
event save set, so wider ordinary-condition and descriptor recovery still needs to rely on the
|
||||||
tracked JSON artifacts until new captures exist
|
grounded static tables and tracked JSON artifacts until new captures exist
|
||||||
|
|
||||||
Do not mix this slice with:
|
Do not mix this slice with:
|
||||||
|
|
||||||
- shell queue/modal behavior
|
- shell queue/modal behavior
|
||||||
- territory-access or selected-profile parity
|
- territory-access or selected-profile parity
|
||||||
- broad condition evaluation without grounded runtime ownership
|
- speculative condition execution without grounded runtime ownership
|
||||||
- speculative executable import for real rows whose descriptor meaning is still weak
|
- speculative executable import for real rows whose descriptor meaning is still weak
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"fixture_id": "packed-event-ordinary-company-finance-overlay-fixture",
|
||||||
|
"source": {
|
||||||
|
"kind": "captured-runtime",
|
||||||
|
"description": "Fixture proving real ordinary Current Cash conditions gate Company Cash through the normal runtime path."
|
||||||
|
},
|
||||||
|
"state_import_path": "packed-event-ordinary-company-finance-overlay.json",
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"kind": "service_trigger_kind",
|
||||||
|
"trigger_kind": 7
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"expected_summary": {
|
||||||
|
"calendar_projection_source": "base-snapshot-preserved",
|
||||||
|
"calendar_projection_is_placeholder": false,
|
||||||
|
"company_count": 3,
|
||||||
|
"active_company_count": 3,
|
||||||
|
"territory_count": 1,
|
||||||
|
"company_territory_track_count": 3,
|
||||||
|
"packed_event_collection_present": true,
|
||||||
|
"packed_event_record_count": 1,
|
||||||
|
"packed_event_decoded_record_count": 1,
|
||||||
|
"packed_event_imported_runtime_record_count": 1,
|
||||||
|
"packed_event_parity_only_record_count": 1,
|
||||||
|
"packed_event_blocked_unmapped_ordinary_condition_count": 0,
|
||||||
|
"event_runtime_record_count": 1,
|
||||||
|
"total_event_record_service_count": 1,
|
||||||
|
"total_trigger_dispatch_count": 1,
|
||||||
|
"total_company_cash": 623
|
||||||
|
},
|
||||||
|
"expected_state_fragment": {
|
||||||
|
"companies": [
|
||||||
|
{
|
||||||
|
"company_id": 1,
|
||||||
|
"current_cash": 333
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"company_id": 2,
|
||||||
|
"current_cash": 90
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"company_id": 3,
|
||||||
|
"current_cash": 200
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packed_event_collection": {
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"import_outcome": "imported",
|
||||||
|
"decoded_conditions": [
|
||||||
|
{
|
||||||
|
"kind": "company_numeric_threshold",
|
||||||
|
"target": {
|
||||||
|
"kind": "selected_company"
|
||||||
|
},
|
||||||
|
"metric": "current_cash",
|
||||||
|
"comparator": "ge",
|
||||||
|
"value": 100
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"event_runtime_records": [
|
||||||
|
{
|
||||||
|
"record_id": 41,
|
||||||
|
"service_count": 1,
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"kind": "company_numeric_threshold",
|
||||||
|
"target": {
|
||||||
|
"kind": "selected_company"
|
||||||
|
},
|
||||||
|
"metric": "current_cash",
|
||||||
|
"comparator": "ge",
|
||||||
|
"value": 100
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"effects": [
|
||||||
|
{
|
||||||
|
"kind": "set_company_cash",
|
||||||
|
"target": {
|
||||||
|
"kind": "condition_true_company"
|
||||||
|
},
|
||||||
|
"value": 333
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"import_id": "packed-event-ordinary-company-finance-overlay",
|
||||||
|
"source": {
|
||||||
|
"description": "Overlay import combining captured company context with the real ordinary company-finance threshold sample."
|
||||||
|
},
|
||||||
|
"base_snapshot_path": "packed-event-ordinary-condition-overlay-base-snapshot.json",
|
||||||
|
"save_slice_path": "packed-event-ordinary-company-finance-save-slice.json"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,139 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"save_slice_id": "packed-event-ordinary-company-finance-save-slice",
|
||||||
|
"source": {
|
||||||
|
"description": "Tracked save-slice document with a real ordinary company-finance threshold row gating Company Cash.",
|
||||||
|
"original_save_filename": "captured-ordinary-company-finance.gms",
|
||||||
|
"original_save_sha256": "ordinary-company-finance-sample-sha256",
|
||||||
|
"notes": [
|
||||||
|
"tracked as JSON save-slice document rather than raw .smp",
|
||||||
|
"proves ordinary Current Cash threshold import through the real packed-event path"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"save_slice": {
|
||||||
|
"file_extension_hint": "gms",
|
||||||
|
"container_profile_family": "rt3-classic-save-container-v1",
|
||||||
|
"mechanism_family": "classic-save-rehydrate-v1",
|
||||||
|
"mechanism_confidence": "grounded",
|
||||||
|
"trailer_family": null,
|
||||||
|
"bridge_family": null,
|
||||||
|
"profile": null,
|
||||||
|
"candidate_availability_table": null,
|
||||||
|
"special_conditions_table": null,
|
||||||
|
"event_runtime_collection": {
|
||||||
|
"source_kind": "packed-event-runtime-collection",
|
||||||
|
"mechanism_family": "classic-save-rehydrate-v1",
|
||||||
|
"mechanism_confidence": "grounded",
|
||||||
|
"container_profile_family": "rt3-classic-save-container-v1",
|
||||||
|
"metadata_tag_offset": 28928,
|
||||||
|
"records_tag_offset": 29184,
|
||||||
|
"close_tag_offset": 29696,
|
||||||
|
"packed_state_version": 1001,
|
||||||
|
"packed_state_version_hex": "0x000003e9",
|
||||||
|
"live_id_bound": 41,
|
||||||
|
"live_record_count": 1,
|
||||||
|
"live_entry_ids": [41],
|
||||||
|
"decoded_record_count": 1,
|
||||||
|
"imported_runtime_record_count": 1,
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"record_index": 0,
|
||||||
|
"live_entry_id": 41,
|
||||||
|
"payload_offset": 29200,
|
||||||
|
"payload_len": 176,
|
||||||
|
"decode_status": "parity_only",
|
||||||
|
"payload_family": "real_packed_v1",
|
||||||
|
"trigger_kind": 7,
|
||||||
|
"one_shot": false,
|
||||||
|
"compact_control": {
|
||||||
|
"mode_byte_0x7ef": 7,
|
||||||
|
"primary_selector_0x7f0": 42,
|
||||||
|
"grouped_mode_0x7f4": 2,
|
||||||
|
"one_shot_header_0x7f5": 0,
|
||||||
|
"modifier_flag_0x7f9": 2,
|
||||||
|
"modifier_flag_0x7fa": 0,
|
||||||
|
"grouped_target_scope_ordinals_0x7fb": [0, 1, 1, 1],
|
||||||
|
"grouped_scope_checkboxes_0x7ff": [1, 0, 0, 0],
|
||||||
|
"summary_toggle_0x800": 1,
|
||||||
|
"grouped_territory_selectors_0x80f": [-1, -1, -1, -1]
|
||||||
|
},
|
||||||
|
"text_bands": [],
|
||||||
|
"standalone_condition_row_count": 1,
|
||||||
|
"standalone_condition_rows": [
|
||||||
|
{
|
||||||
|
"row_index": 0,
|
||||||
|
"raw_condition_id": 1802,
|
||||||
|
"subtype": 0,
|
||||||
|
"flag_bytes": [100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
"candidate_name": null,
|
||||||
|
"comparator": "ge",
|
||||||
|
"metric": "Current Cash",
|
||||||
|
"semantic_family": "numeric_threshold",
|
||||||
|
"semantic_preview": "Test Current Cash >= 100",
|
||||||
|
"requires_candidate_name_binding": false,
|
||||||
|
"notes": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"negative_sentinel_scope": {
|
||||||
|
"company_test_scope": "selected_company_only",
|
||||||
|
"player_test_scope": "disabled",
|
||||||
|
"territory_scope_selector_is_0x63": false,
|
||||||
|
"source_row_indexes": [0]
|
||||||
|
},
|
||||||
|
"grouped_effect_row_counts": [1, 0, 0, 0],
|
||||||
|
"grouped_effect_rows": [
|
||||||
|
{
|
||||||
|
"group_index": 0,
|
||||||
|
"row_index": 0,
|
||||||
|
"descriptor_id": 2,
|
||||||
|
"descriptor_label": "Company Cash",
|
||||||
|
"target_mask_bits": 1,
|
||||||
|
"parameter_family": "company_finance_scalar",
|
||||||
|
"opcode": 8,
|
||||||
|
"raw_scalar_value": 333,
|
||||||
|
"value_byte_0x09": 1,
|
||||||
|
"value_dword_0x0d": 12,
|
||||||
|
"value_byte_0x11": 2,
|
||||||
|
"value_byte_0x12": 3,
|
||||||
|
"value_word_0x14": 24,
|
||||||
|
"value_word_0x16": 36,
|
||||||
|
"row_shape": "multivalue_scalar",
|
||||||
|
"semantic_family": "multivalue_scalar",
|
||||||
|
"semantic_preview": "Set Company Cash to 333 with aux [2, 3, 24, 36]",
|
||||||
|
"locomotive_name": null,
|
||||||
|
"notes": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"decoded_conditions": [
|
||||||
|
{
|
||||||
|
"kind": "company_numeric_threshold",
|
||||||
|
"target": {
|
||||||
|
"kind": "condition_true_company"
|
||||||
|
},
|
||||||
|
"metric": "current_cash",
|
||||||
|
"comparator": "ge",
|
||||||
|
"value": 100
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"decoded_actions": [
|
||||||
|
{
|
||||||
|
"kind": "set_company_cash",
|
||||||
|
"target": {
|
||||||
|
"kind": "condition_true_company"
|
||||||
|
},
|
||||||
|
"value": 333
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"executable_import_ready": true,
|
||||||
|
"notes": [
|
||||||
|
"decoded from grounded real 0x4e9a row framing",
|
||||||
|
"ordinary Current Cash threshold lowers condition-relative company scope at import time"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notes": [
|
||||||
|
"real ordinary company-finance threshold sample"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"fixture_id": "packed-event-ordinary-company-territory-track-overlay-fixture",
|
||||||
|
"source": {
|
||||||
|
"kind": "captured-runtime",
|
||||||
|
"description": "Fixture proving real ordinary company-territory thresholds gate Company Cash through the normal runtime path."
|
||||||
|
},
|
||||||
|
"state_import_path": "packed-event-ordinary-company-territory-track-overlay.json",
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"kind": "service_trigger_kind",
|
||||||
|
"trigger_kind": 7
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"expected_summary": {
|
||||||
|
"calendar_projection_source": "base-snapshot-preserved",
|
||||||
|
"calendar_projection_is_placeholder": false,
|
||||||
|
"company_count": 3,
|
||||||
|
"active_company_count": 3,
|
||||||
|
"territory_count": 1,
|
||||||
|
"company_territory_track_count": 3,
|
||||||
|
"packed_event_collection_present": true,
|
||||||
|
"packed_event_record_count": 1,
|
||||||
|
"packed_event_decoded_record_count": 1,
|
||||||
|
"packed_event_imported_runtime_record_count": 1,
|
||||||
|
"packed_event_parity_only_record_count": 1,
|
||||||
|
"packed_event_blocked_missing_territory_context_count": 0,
|
||||||
|
"event_runtime_record_count": 1,
|
||||||
|
"total_event_record_service_count": 1,
|
||||||
|
"total_trigger_dispatch_count": 1,
|
||||||
|
"total_company_cash": 845
|
||||||
|
},
|
||||||
|
"expected_state_fragment": {
|
||||||
|
"companies": [
|
||||||
|
{
|
||||||
|
"company_id": 1,
|
||||||
|
"current_cash": 555
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"company_id": 2,
|
||||||
|
"current_cash": 90
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"company_id": 3,
|
||||||
|
"current_cash": 200
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packed_event_collection": {
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"import_outcome": "imported",
|
||||||
|
"decoded_conditions": [
|
||||||
|
{
|
||||||
|
"kind": "company_territory_numeric_threshold",
|
||||||
|
"target": {
|
||||||
|
"kind": "selected_company"
|
||||||
|
},
|
||||||
|
"metric": "total",
|
||||||
|
"comparator": "ge",
|
||||||
|
"value": 10
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"import_id": "packed-event-ordinary-company-territory-track-overlay",
|
||||||
|
"source": {
|
||||||
|
"description": "Overlay import combining company and territory context with the real ordinary company-territory threshold sample."
|
||||||
|
},
|
||||||
|
"base_snapshot_path": "packed-event-ordinary-condition-overlay-base-snapshot.json",
|
||||||
|
"save_slice_path": "packed-event-ordinary-company-territory-track-save-slice.json"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,139 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"save_slice_id": "packed-event-ordinary-company-territory-track-save-slice",
|
||||||
|
"source": {
|
||||||
|
"description": "Tracked save-slice document with a real ordinary company-territory threshold row gating Company Cash.",
|
||||||
|
"original_save_filename": "captured-ordinary-company-territory-track.gms",
|
||||||
|
"original_save_sha256": "ordinary-company-territory-track-sample-sha256",
|
||||||
|
"notes": [
|
||||||
|
"tracked as JSON save-slice document rather than raw .smp",
|
||||||
|
"proves company-territory thresholds import when both company and territory overlay context exist"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"save_slice": {
|
||||||
|
"file_extension_hint": "gms",
|
||||||
|
"container_profile_family": "rt3-classic-save-container-v1",
|
||||||
|
"mechanism_family": "classic-save-rehydrate-v1",
|
||||||
|
"mechanism_confidence": "grounded",
|
||||||
|
"trailer_family": null,
|
||||||
|
"bridge_family": null,
|
||||||
|
"profile": null,
|
||||||
|
"candidate_availability_table": null,
|
||||||
|
"special_conditions_table": null,
|
||||||
|
"event_runtime_collection": {
|
||||||
|
"source_kind": "packed-event-runtime-collection",
|
||||||
|
"mechanism_family": "classic-save-rehydrate-v1",
|
||||||
|
"mechanism_confidence": "grounded",
|
||||||
|
"container_profile_family": "rt3-classic-save-container-v1",
|
||||||
|
"metadata_tag_offset": 28928,
|
||||||
|
"records_tag_offset": 29184,
|
||||||
|
"close_tag_offset": 29696,
|
||||||
|
"packed_state_version": 1001,
|
||||||
|
"packed_state_version_hex": "0x000003e9",
|
||||||
|
"live_id_bound": 44,
|
||||||
|
"live_record_count": 1,
|
||||||
|
"live_entry_ids": [44],
|
||||||
|
"decoded_record_count": 1,
|
||||||
|
"imported_runtime_record_count": 1,
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"record_index": 0,
|
||||||
|
"live_entry_id": 44,
|
||||||
|
"payload_offset": 29296,
|
||||||
|
"payload_len": 176,
|
||||||
|
"decode_status": "parity_only",
|
||||||
|
"payload_family": "real_packed_v1",
|
||||||
|
"trigger_kind": 7,
|
||||||
|
"one_shot": false,
|
||||||
|
"compact_control": {
|
||||||
|
"mode_byte_0x7ef": 7,
|
||||||
|
"primary_selector_0x7f0": 99,
|
||||||
|
"grouped_mode_0x7f4": 2,
|
||||||
|
"one_shot_header_0x7f5": 0,
|
||||||
|
"modifier_flag_0x7f9": 2,
|
||||||
|
"modifier_flag_0x7fa": 0,
|
||||||
|
"grouped_target_scope_ordinals_0x7fb": [0, 1, 1, 1],
|
||||||
|
"grouped_scope_checkboxes_0x7ff": [1, 0, 0, 0],
|
||||||
|
"summary_toggle_0x800": 1,
|
||||||
|
"grouped_territory_selectors_0x80f": [-1, -1, -1, -1]
|
||||||
|
},
|
||||||
|
"text_bands": [],
|
||||||
|
"standalone_condition_row_count": 1,
|
||||||
|
"standalone_condition_rows": [
|
||||||
|
{
|
||||||
|
"row_index": 0,
|
||||||
|
"raw_condition_id": 2323,
|
||||||
|
"subtype": 0,
|
||||||
|
"flag_bytes": [10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
"candidate_name": null,
|
||||||
|
"comparator": "ge",
|
||||||
|
"metric": "Company-Territory Track Pieces",
|
||||||
|
"semantic_family": "numeric_threshold",
|
||||||
|
"semantic_preview": "Test Company-Territory Track Pieces >= 10",
|
||||||
|
"requires_candidate_name_binding": false,
|
||||||
|
"notes": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"negative_sentinel_scope": {
|
||||||
|
"company_test_scope": "selected_company_only",
|
||||||
|
"player_test_scope": "disabled",
|
||||||
|
"territory_scope_selector_is_0x63": true,
|
||||||
|
"source_row_indexes": [0]
|
||||||
|
},
|
||||||
|
"grouped_effect_row_counts": [1, 0, 0, 0],
|
||||||
|
"grouped_effect_rows": [
|
||||||
|
{
|
||||||
|
"group_index": 0,
|
||||||
|
"row_index": 0,
|
||||||
|
"descriptor_id": 2,
|
||||||
|
"descriptor_label": "Company Cash",
|
||||||
|
"target_mask_bits": 1,
|
||||||
|
"parameter_family": "company_finance_scalar",
|
||||||
|
"opcode": 8,
|
||||||
|
"raw_scalar_value": 555,
|
||||||
|
"value_byte_0x09": 1,
|
||||||
|
"value_dword_0x0d": 12,
|
||||||
|
"value_byte_0x11": 2,
|
||||||
|
"value_byte_0x12": 3,
|
||||||
|
"value_word_0x14": 24,
|
||||||
|
"value_word_0x16": 36,
|
||||||
|
"row_shape": "multivalue_scalar",
|
||||||
|
"semantic_family": "multivalue_scalar",
|
||||||
|
"semantic_preview": "Set Company Cash to 555 with aux [2, 3, 24, 36]",
|
||||||
|
"locomotive_name": null,
|
||||||
|
"notes": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"decoded_conditions": [
|
||||||
|
{
|
||||||
|
"kind": "company_territory_numeric_threshold",
|
||||||
|
"target": {
|
||||||
|
"kind": "condition_true_company"
|
||||||
|
},
|
||||||
|
"metric": "total",
|
||||||
|
"comparator": "ge",
|
||||||
|
"value": 10
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"decoded_actions": [
|
||||||
|
{
|
||||||
|
"kind": "set_company_cash",
|
||||||
|
"target": {
|
||||||
|
"kind": "condition_true_company"
|
||||||
|
},
|
||||||
|
"value": 555
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"executable_import_ready": true,
|
||||||
|
"notes": [
|
||||||
|
"decoded from grounded real 0x4e9a row framing",
|
||||||
|
"company-territory thresholds lower condition-relative company scope when overlay territory context is available"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notes": [
|
||||||
|
"real ordinary company-territory threshold sample"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"fixture_id": "packed-event-ordinary-company-track-overlay-fixture",
|
||||||
|
"source": {
|
||||||
|
"kind": "captured-runtime",
|
||||||
|
"description": "Fixture proving real ordinary company-track thresholds gate Company Track Pieces Buildable through the normal runtime path."
|
||||||
|
},
|
||||||
|
"state_import_path": "packed-event-ordinary-company-track-overlay.json",
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"kind": "service_trigger_kind",
|
||||||
|
"trigger_kind": 7
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"expected_summary": {
|
||||||
|
"calendar_projection_source": "base-snapshot-preserved",
|
||||||
|
"calendar_projection_is_placeholder": false,
|
||||||
|
"company_count": 3,
|
||||||
|
"active_company_count": 3,
|
||||||
|
"territory_count": 1,
|
||||||
|
"company_territory_track_count": 3,
|
||||||
|
"packed_event_collection_present": true,
|
||||||
|
"packed_event_record_count": 1,
|
||||||
|
"packed_event_decoded_record_count": 1,
|
||||||
|
"packed_event_imported_runtime_record_count": 1,
|
||||||
|
"packed_event_parity_only_record_count": 1,
|
||||||
|
"event_runtime_record_count": 1,
|
||||||
|
"total_event_record_service_count": 1,
|
||||||
|
"total_trigger_dispatch_count": 1,
|
||||||
|
"total_company_cash": 440
|
||||||
|
},
|
||||||
|
"expected_state_fragment": {
|
||||||
|
"companies": [
|
||||||
|
{
|
||||||
|
"company_id": 1,
|
||||||
|
"available_track_laying_capacity": 12
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"company_id": 2,
|
||||||
|
"available_track_laying_capacity": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"company_id": 3,
|
||||||
|
"available_track_laying_capacity": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"event_runtime_records": [
|
||||||
|
{
|
||||||
|
"record_id": 42,
|
||||||
|
"service_count": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"import_id": "packed-event-ordinary-company-track-overlay",
|
||||||
|
"source": {
|
||||||
|
"description": "Overlay import combining captured company context with the real ordinary company-track threshold sample."
|
||||||
|
},
|
||||||
|
"base_snapshot_path": "packed-event-ordinary-condition-overlay-base-snapshot.json",
|
||||||
|
"save_slice_path": "packed-event-ordinary-company-track-save-slice.json"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,139 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"save_slice_id": "packed-event-ordinary-company-track-save-slice",
|
||||||
|
"source": {
|
||||||
|
"description": "Tracked save-slice document with a real ordinary company-track threshold row gating Company Track Pieces Buildable.",
|
||||||
|
"original_save_filename": "captured-ordinary-company-track.gms",
|
||||||
|
"original_save_sha256": "ordinary-company-track-sample-sha256",
|
||||||
|
"notes": [
|
||||||
|
"tracked as JSON save-slice document rather than raw .smp",
|
||||||
|
"proves ordinary Company Track Pieces threshold import through the real packed-event path"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"save_slice": {
|
||||||
|
"file_extension_hint": "gms",
|
||||||
|
"container_profile_family": "rt3-classic-save-container-v1",
|
||||||
|
"mechanism_family": "classic-save-rehydrate-v1",
|
||||||
|
"mechanism_confidence": "grounded",
|
||||||
|
"trailer_family": null,
|
||||||
|
"bridge_family": null,
|
||||||
|
"profile": null,
|
||||||
|
"candidate_availability_table": null,
|
||||||
|
"special_conditions_table": null,
|
||||||
|
"event_runtime_collection": {
|
||||||
|
"source_kind": "packed-event-runtime-collection",
|
||||||
|
"mechanism_family": "classic-save-rehydrate-v1",
|
||||||
|
"mechanism_confidence": "grounded",
|
||||||
|
"container_profile_family": "rt3-classic-save-container-v1",
|
||||||
|
"metadata_tag_offset": 28928,
|
||||||
|
"records_tag_offset": 29184,
|
||||||
|
"close_tag_offset": 29696,
|
||||||
|
"packed_state_version": 1001,
|
||||||
|
"packed_state_version_hex": "0x000003e9",
|
||||||
|
"live_id_bound": 42,
|
||||||
|
"live_record_count": 1,
|
||||||
|
"live_entry_ids": [42],
|
||||||
|
"decoded_record_count": 1,
|
||||||
|
"imported_runtime_record_count": 1,
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"record_index": 0,
|
||||||
|
"live_entry_id": 42,
|
||||||
|
"payload_offset": 29232,
|
||||||
|
"payload_len": 176,
|
||||||
|
"decode_status": "parity_only",
|
||||||
|
"payload_family": "real_packed_v1",
|
||||||
|
"trigger_kind": 7,
|
||||||
|
"one_shot": false,
|
||||||
|
"compact_control": {
|
||||||
|
"mode_byte_0x7ef": 7,
|
||||||
|
"primary_selector_0x7f0": 42,
|
||||||
|
"grouped_mode_0x7f4": 2,
|
||||||
|
"one_shot_header_0x7f5": 0,
|
||||||
|
"modifier_flag_0x7f9": 2,
|
||||||
|
"modifier_flag_0x7fa": 0,
|
||||||
|
"grouped_target_scope_ordinals_0x7fb": [0, 1, 1, 1],
|
||||||
|
"grouped_scope_checkboxes_0x7ff": [1, 0, 0, 0],
|
||||||
|
"summary_toggle_0x800": 1,
|
||||||
|
"grouped_territory_selectors_0x80f": [-1, -1, -1, -1]
|
||||||
|
},
|
||||||
|
"text_bands": [],
|
||||||
|
"standalone_condition_row_count": 1,
|
||||||
|
"standalone_condition_rows": [
|
||||||
|
{
|
||||||
|
"row_index": 0,
|
||||||
|
"raw_condition_id": 2293,
|
||||||
|
"subtype": 0,
|
||||||
|
"flag_bytes": [20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
"candidate_name": null,
|
||||||
|
"comparator": "ge",
|
||||||
|
"metric": "Company Track Pieces",
|
||||||
|
"semantic_family": "numeric_threshold",
|
||||||
|
"semantic_preview": "Test Company Track Pieces >= 20",
|
||||||
|
"requires_candidate_name_binding": false,
|
||||||
|
"notes": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"negative_sentinel_scope": {
|
||||||
|
"company_test_scope": "selected_company_only",
|
||||||
|
"player_test_scope": "disabled",
|
||||||
|
"territory_scope_selector_is_0x63": false,
|
||||||
|
"source_row_indexes": [0]
|
||||||
|
},
|
||||||
|
"grouped_effect_row_counts": [1, 0, 0, 0],
|
||||||
|
"grouped_effect_rows": [
|
||||||
|
{
|
||||||
|
"group_index": 0,
|
||||||
|
"row_index": 0,
|
||||||
|
"descriptor_id": 16,
|
||||||
|
"descriptor_label": "Company Track Pieces Buildable",
|
||||||
|
"target_mask_bits": 1,
|
||||||
|
"parameter_family": "company_build_limit_scalar",
|
||||||
|
"opcode": 3,
|
||||||
|
"raw_scalar_value": 12,
|
||||||
|
"value_byte_0x09": 0,
|
||||||
|
"value_dword_0x0d": 0,
|
||||||
|
"value_byte_0x11": 0,
|
||||||
|
"value_byte_0x12": 0,
|
||||||
|
"value_word_0x14": 0,
|
||||||
|
"value_word_0x16": 0,
|
||||||
|
"row_shape": "scalar_assignment",
|
||||||
|
"semantic_family": "scalar_assignment",
|
||||||
|
"semantic_preview": "Set Company Track Pieces Buildable to 12",
|
||||||
|
"locomotive_name": null,
|
||||||
|
"notes": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"decoded_conditions": [
|
||||||
|
{
|
||||||
|
"kind": "company_numeric_threshold",
|
||||||
|
"target": {
|
||||||
|
"kind": "condition_true_company"
|
||||||
|
},
|
||||||
|
"metric": "track_pieces_total",
|
||||||
|
"comparator": "ge",
|
||||||
|
"value": 20
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"decoded_actions": [
|
||||||
|
{
|
||||||
|
"kind": "set_company_track_laying_capacity",
|
||||||
|
"target": {
|
||||||
|
"kind": "condition_true_company"
|
||||||
|
},
|
||||||
|
"value": 12
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"executable_import_ready": true,
|
||||||
|
"notes": [
|
||||||
|
"decoded from grounded real 0x4e9a row framing",
|
||||||
|
"ordinary Company Track Pieces threshold lowers condition-relative company scope at import time"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notes": [
|
||||||
|
"real ordinary company-track threshold sample"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,132 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"snapshot_id": "packed-event-ordinary-condition-overlay-base-snapshot",
|
||||||
|
"source": {
|
||||||
|
"description": "Base runtime snapshot supplying company, selection, and aggregate territory context for ordinary-condition packed-event overlays."
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"calendar": {
|
||||||
|
"year": 1840,
|
||||||
|
"month_slot": 1,
|
||||||
|
"phase_slot": 2,
|
||||||
|
"tick_slot": 3
|
||||||
|
},
|
||||||
|
"world_flags": {
|
||||||
|
"base.only": true
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"base.note": "preserve ordinary-condition overlay context"
|
||||||
|
},
|
||||||
|
"companies": [
|
||||||
|
{
|
||||||
|
"company_id": 1,
|
||||||
|
"current_cash": 150,
|
||||||
|
"debt": 80,
|
||||||
|
"credit_rating_score": 650,
|
||||||
|
"prime_rate": 5,
|
||||||
|
"controller_kind": "human",
|
||||||
|
"track_piece_counts": {
|
||||||
|
"total": 20,
|
||||||
|
"single": 5,
|
||||||
|
"double": 8,
|
||||||
|
"transition": 1,
|
||||||
|
"electric": 3,
|
||||||
|
"non_electric": 17
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"company_id": 2,
|
||||||
|
"current_cash": 90,
|
||||||
|
"debt": 40,
|
||||||
|
"credit_rating_score": 480,
|
||||||
|
"prime_rate": 6,
|
||||||
|
"controller_kind": "ai",
|
||||||
|
"track_piece_counts": {
|
||||||
|
"total": 8,
|
||||||
|
"single": 2,
|
||||||
|
"double": 2,
|
||||||
|
"transition": 0,
|
||||||
|
"electric": 1,
|
||||||
|
"non_electric": 7
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"company_id": 3,
|
||||||
|
"current_cash": 200,
|
||||||
|
"debt": 10,
|
||||||
|
"credit_rating_score": 720,
|
||||||
|
"prime_rate": 4,
|
||||||
|
"controller_kind": "human",
|
||||||
|
"track_piece_counts": {
|
||||||
|
"total": 30,
|
||||||
|
"single": 10,
|
||||||
|
"double": 12,
|
||||||
|
"transition": 2,
|
||||||
|
"electric": 8,
|
||||||
|
"non_electric": 22
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selected_company_id": 1,
|
||||||
|
"territories": [
|
||||||
|
{
|
||||||
|
"territory_id": 7,
|
||||||
|
"track_piece_counts": {
|
||||||
|
"total": 50,
|
||||||
|
"single": 10,
|
||||||
|
"double": 20,
|
||||||
|
"transition": 5,
|
||||||
|
"electric": 15,
|
||||||
|
"non_electric": 35
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"company_territory_track_piece_counts": [
|
||||||
|
{
|
||||||
|
"company_id": 1,
|
||||||
|
"territory_id": 7,
|
||||||
|
"track_piece_counts": {
|
||||||
|
"total": 12,
|
||||||
|
"single": 3,
|
||||||
|
"double": 5,
|
||||||
|
"transition": 1,
|
||||||
|
"electric": 4,
|
||||||
|
"non_electric": 8
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"company_id": 2,
|
||||||
|
"territory_id": 7,
|
||||||
|
"track_piece_counts": {
|
||||||
|
"total": 7,
|
||||||
|
"single": 2,
|
||||||
|
"double": 2,
|
||||||
|
"transition": 0,
|
||||||
|
"electric": 1,
|
||||||
|
"non_electric": 6
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"company_id": 3,
|
||||||
|
"territory_id": 7,
|
||||||
|
"track_piece_counts": {
|
||||||
|
"total": 15,
|
||||||
|
"single": 5,
|
||||||
|
"double": 6,
|
||||||
|
"transition": 2,
|
||||||
|
"electric": 5,
|
||||||
|
"non_electric": 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"event_runtime_records": [],
|
||||||
|
"candidate_availability": {},
|
||||||
|
"special_conditions": {},
|
||||||
|
"service_state": {
|
||||||
|
"periodic_boundary_calls": 0,
|
||||||
|
"trigger_dispatch_counts": {},
|
||||||
|
"total_event_record_services": 0,
|
||||||
|
"dirty_rerun_count": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"fixture_id": "packed-event-ordinary-named-territory-overlay-fixture",
|
||||||
|
"source": {
|
||||||
|
"kind": "captured-runtime",
|
||||||
|
"description": "Fixture proving named-territory ordinary conditions stay parity-only with an explicit blocker."
|
||||||
|
},
|
||||||
|
"state_import_path": "packed-event-ordinary-named-territory-overlay.json",
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"kind": "service_trigger_kind",
|
||||||
|
"trigger_kind": 7
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"expected_summary": {
|
||||||
|
"calendar_projection_source": "base-snapshot-preserved",
|
||||||
|
"calendar_projection_is_placeholder": false,
|
||||||
|
"company_count": 3,
|
||||||
|
"active_company_count": 3,
|
||||||
|
"territory_count": 1,
|
||||||
|
"company_territory_track_count": 3,
|
||||||
|
"packed_event_collection_present": true,
|
||||||
|
"packed_event_record_count": 1,
|
||||||
|
"packed_event_decoded_record_count": 1,
|
||||||
|
"packed_event_imported_runtime_record_count": 0,
|
||||||
|
"packed_event_parity_only_record_count": 1,
|
||||||
|
"packed_event_blocked_named_territory_binding_count": 1,
|
||||||
|
"event_runtime_record_count": 0,
|
||||||
|
"total_event_record_service_count": 0,
|
||||||
|
"total_trigger_dispatch_count": 1,
|
||||||
|
"total_company_cash": 440
|
||||||
|
},
|
||||||
|
"expected_state_fragment": {
|
||||||
|
"companies": [
|
||||||
|
{
|
||||||
|
"company_id": 1,
|
||||||
|
"current_cash": 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"company_id": 2,
|
||||||
|
"current_cash": 90
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"company_id": 3,
|
||||||
|
"current_cash": 200
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packed_event_collection": {
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"import_outcome": "blocked_named_territory_binding",
|
||||||
|
"standalone_condition_rows": [
|
||||||
|
{
|
||||||
|
"candidate_name": "Appalachia",
|
||||||
|
"requires_candidate_name_binding": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"event_runtime_records": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"import_id": "packed-event-ordinary-named-territory-overlay",
|
||||||
|
"source": {
|
||||||
|
"description": "Overlay import combining aggregate territory context with the real named-territory threshold sample."
|
||||||
|
},
|
||||||
|
"base_snapshot_path": "packed-event-ordinary-condition-overlay-base-snapshot.json",
|
||||||
|
"save_slice_path": "packed-event-ordinary-named-territory-save-slice.json"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,132 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"save_slice_id": "packed-event-ordinary-named-territory-save-slice",
|
||||||
|
"source": {
|
||||||
|
"description": "Tracked save-slice document with a real ordinary named-territory threshold row that stays parity-only.",
|
||||||
|
"original_save_filename": "captured-ordinary-named-territory.gms",
|
||||||
|
"original_save_sha256": "ordinary-named-territory-sample-sha256",
|
||||||
|
"notes": [
|
||||||
|
"tracked as JSON save-slice document rather than raw .smp",
|
||||||
|
"locks the named-territory binding blocker for ordinary condition rows"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"save_slice": {
|
||||||
|
"file_extension_hint": "gms",
|
||||||
|
"container_profile_family": "rt3-classic-save-container-v1",
|
||||||
|
"mechanism_family": "classic-save-rehydrate-v1",
|
||||||
|
"mechanism_confidence": "grounded",
|
||||||
|
"trailer_family": null,
|
||||||
|
"bridge_family": null,
|
||||||
|
"profile": null,
|
||||||
|
"candidate_availability_table": null,
|
||||||
|
"special_conditions_table": null,
|
||||||
|
"event_runtime_collection": {
|
||||||
|
"source_kind": "packed-event-runtime-collection",
|
||||||
|
"mechanism_family": "classic-save-rehydrate-v1",
|
||||||
|
"mechanism_confidence": "grounded",
|
||||||
|
"container_profile_family": "rt3-classic-save-container-v1",
|
||||||
|
"metadata_tag_offset": 28928,
|
||||||
|
"records_tag_offset": 29184,
|
||||||
|
"close_tag_offset": 29696,
|
||||||
|
"packed_state_version": 1001,
|
||||||
|
"packed_state_version_hex": "0x000003e9",
|
||||||
|
"live_id_bound": 45,
|
||||||
|
"live_record_count": 1,
|
||||||
|
"live_entry_ids": [45],
|
||||||
|
"decoded_record_count": 1,
|
||||||
|
"imported_runtime_record_count": 0,
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"record_index": 0,
|
||||||
|
"live_entry_id": 45,
|
||||||
|
"payload_offset": 29328,
|
||||||
|
"payload_len": 186,
|
||||||
|
"decode_status": "parity_only",
|
||||||
|
"payload_family": "real_packed_v1",
|
||||||
|
"trigger_kind": 7,
|
||||||
|
"one_shot": false,
|
||||||
|
"compact_control": {
|
||||||
|
"mode_byte_0x7ef": 7,
|
||||||
|
"primary_selector_0x7f0": 99,
|
||||||
|
"grouped_mode_0x7f4": 2,
|
||||||
|
"one_shot_header_0x7f5": 0,
|
||||||
|
"modifier_flag_0x7f9": 0,
|
||||||
|
"modifier_flag_0x7fa": 0,
|
||||||
|
"grouped_target_scope_ordinals_0x7fb": [1, 1, 1, 1],
|
||||||
|
"grouped_scope_checkboxes_0x7ff": [1, 0, 0, 0],
|
||||||
|
"summary_toggle_0x800": 1,
|
||||||
|
"grouped_territory_selectors_0x80f": [-1, -1, -1, -1]
|
||||||
|
},
|
||||||
|
"text_bands": [],
|
||||||
|
"standalone_condition_row_count": 1,
|
||||||
|
"standalone_condition_rows": [
|
||||||
|
{
|
||||||
|
"row_index": 0,
|
||||||
|
"raw_condition_id": 2313,
|
||||||
|
"subtype": 0,
|
||||||
|
"flag_bytes": [10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
"candidate_name": "Appalachia",
|
||||||
|
"comparator": "ge",
|
||||||
|
"metric": "Territory Track Pieces",
|
||||||
|
"semantic_family": "numeric_threshold",
|
||||||
|
"semantic_preview": "Test Territory Track Pieces >= 10",
|
||||||
|
"requires_candidate_name_binding": true,
|
||||||
|
"notes": [
|
||||||
|
"condition row carries candidate-name side string"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"grouped_effect_row_counts": [1, 0, 0, 0],
|
||||||
|
"grouped_effect_rows": [
|
||||||
|
{
|
||||||
|
"group_index": 0,
|
||||||
|
"row_index": 0,
|
||||||
|
"descriptor_id": 2,
|
||||||
|
"descriptor_label": "Company Cash",
|
||||||
|
"target_mask_bits": 1,
|
||||||
|
"parameter_family": "company_finance_scalar",
|
||||||
|
"opcode": 8,
|
||||||
|
"raw_scalar_value": 777,
|
||||||
|
"value_byte_0x09": 1,
|
||||||
|
"value_dword_0x0d": 12,
|
||||||
|
"value_byte_0x11": 2,
|
||||||
|
"value_byte_0x12": 3,
|
||||||
|
"value_word_0x14": 24,
|
||||||
|
"value_word_0x16": 36,
|
||||||
|
"row_shape": "multivalue_scalar",
|
||||||
|
"semantic_family": "multivalue_scalar",
|
||||||
|
"semantic_preview": "Set Company Cash to 777 with aux [2, 3, 24, 36]",
|
||||||
|
"locomotive_name": null,
|
||||||
|
"notes": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"decoded_conditions": [
|
||||||
|
{
|
||||||
|
"kind": "territory_numeric_threshold",
|
||||||
|
"metric": "track_pieces_total",
|
||||||
|
"comparator": "ge",
|
||||||
|
"value": 10
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"decoded_actions": [
|
||||||
|
{
|
||||||
|
"kind": "set_company_cash",
|
||||||
|
"target": {
|
||||||
|
"kind": "selected_company"
|
||||||
|
},
|
||||||
|
"value": 777
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"executable_import_ready": true,
|
||||||
|
"notes": [
|
||||||
|
"decoded from grounded real 0x4e9a row framing",
|
||||||
|
"candidate-name territory binding remains parity-only in this slice"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notes": [
|
||||||
|
"real ordinary named-territory threshold parity sample"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"fixture_id": "packed-event-ordinary-territory-track-overlay-fixture",
|
||||||
|
"source": {
|
||||||
|
"kind": "captured-runtime",
|
||||||
|
"description": "Fixture proving aggregate territory thresholds can gate real packed-event execution when overlay territory context is present."
|
||||||
|
},
|
||||||
|
"state_import_path": "packed-event-ordinary-territory-track-overlay.json",
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"kind": "service_trigger_kind",
|
||||||
|
"trigger_kind": 7
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"expected_summary": {
|
||||||
|
"calendar_projection_source": "base-snapshot-preserved",
|
||||||
|
"calendar_projection_is_placeholder": false,
|
||||||
|
"company_count": 3,
|
||||||
|
"active_company_count": 3,
|
||||||
|
"territory_count": 1,
|
||||||
|
"company_territory_track_count": 3,
|
||||||
|
"packed_event_collection_present": true,
|
||||||
|
"packed_event_record_count": 1,
|
||||||
|
"packed_event_decoded_record_count": 1,
|
||||||
|
"packed_event_imported_runtime_record_count": 1,
|
||||||
|
"packed_event_parity_only_record_count": 1,
|
||||||
|
"packed_event_blocked_missing_territory_context_count": 0,
|
||||||
|
"event_runtime_record_count": 1,
|
||||||
|
"total_event_record_service_count": 1,
|
||||||
|
"total_trigger_dispatch_count": 1,
|
||||||
|
"total_company_cash": 734
|
||||||
|
},
|
||||||
|
"expected_state_fragment": {
|
||||||
|
"companies": [
|
||||||
|
{
|
||||||
|
"company_id": 1,
|
||||||
|
"current_cash": 444
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"company_id": 2,
|
||||||
|
"current_cash": 90
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"company_id": 3,
|
||||||
|
"current_cash": 200
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packed_event_collection": {
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"import_outcome": "imported",
|
||||||
|
"decoded_conditions": [
|
||||||
|
{
|
||||||
|
"kind": "territory_numeric_threshold",
|
||||||
|
"metric": "track_pieces_total",
|
||||||
|
"comparator": "ge",
|
||||||
|
"value": 40
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"import_id": "packed-event-ordinary-territory-track-overlay",
|
||||||
|
"source": {
|
||||||
|
"description": "Overlay import combining aggregate territory context with the real ordinary territory-track threshold sample."
|
||||||
|
},
|
||||||
|
"base_snapshot_path": "packed-event-ordinary-condition-overlay-base-snapshot.json",
|
||||||
|
"save_slice_path": "packed-event-ordinary-territory-track-save-slice.json"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,130 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"save_slice_id": "packed-event-ordinary-territory-track-save-slice",
|
||||||
|
"source": {
|
||||||
|
"description": "Tracked save-slice document with a real ordinary territory-track threshold row gating Company Cash.",
|
||||||
|
"original_save_filename": "captured-ordinary-territory-track.gms",
|
||||||
|
"original_save_sha256": "ordinary-territory-track-sample-sha256",
|
||||||
|
"notes": [
|
||||||
|
"tracked as JSON save-slice document rather than raw .smp",
|
||||||
|
"proves aggregate territory thresholds import when overlay territory context exists"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"save_slice": {
|
||||||
|
"file_extension_hint": "gms",
|
||||||
|
"container_profile_family": "rt3-classic-save-container-v1",
|
||||||
|
"mechanism_family": "classic-save-rehydrate-v1",
|
||||||
|
"mechanism_confidence": "grounded",
|
||||||
|
"trailer_family": null,
|
||||||
|
"bridge_family": null,
|
||||||
|
"profile": null,
|
||||||
|
"candidate_availability_table": null,
|
||||||
|
"special_conditions_table": null,
|
||||||
|
"event_runtime_collection": {
|
||||||
|
"source_kind": "packed-event-runtime-collection",
|
||||||
|
"mechanism_family": "classic-save-rehydrate-v1",
|
||||||
|
"mechanism_confidence": "grounded",
|
||||||
|
"container_profile_family": "rt3-classic-save-container-v1",
|
||||||
|
"metadata_tag_offset": 28928,
|
||||||
|
"records_tag_offset": 29184,
|
||||||
|
"close_tag_offset": 29696,
|
||||||
|
"packed_state_version": 1001,
|
||||||
|
"packed_state_version_hex": "0x000003e9",
|
||||||
|
"live_id_bound": 43,
|
||||||
|
"live_record_count": 1,
|
||||||
|
"live_entry_ids": [43],
|
||||||
|
"decoded_record_count": 1,
|
||||||
|
"imported_runtime_record_count": 1,
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"record_index": 0,
|
||||||
|
"live_entry_id": 43,
|
||||||
|
"payload_offset": 29264,
|
||||||
|
"payload_len": 176,
|
||||||
|
"decode_status": "parity_only",
|
||||||
|
"payload_family": "real_packed_v1",
|
||||||
|
"trigger_kind": 7,
|
||||||
|
"one_shot": false,
|
||||||
|
"compact_control": {
|
||||||
|
"mode_byte_0x7ef": 7,
|
||||||
|
"primary_selector_0x7f0": 99,
|
||||||
|
"grouped_mode_0x7f4": 2,
|
||||||
|
"one_shot_header_0x7f5": 0,
|
||||||
|
"modifier_flag_0x7f9": 0,
|
||||||
|
"modifier_flag_0x7fa": 0,
|
||||||
|
"grouped_target_scope_ordinals_0x7fb": [1, 1, 1, 1],
|
||||||
|
"grouped_scope_checkboxes_0x7ff": [1, 0, 0, 0],
|
||||||
|
"summary_toggle_0x800": 1,
|
||||||
|
"grouped_territory_selectors_0x80f": [-1, -1, -1, -1]
|
||||||
|
},
|
||||||
|
"text_bands": [],
|
||||||
|
"standalone_condition_row_count": 1,
|
||||||
|
"standalone_condition_rows": [
|
||||||
|
{
|
||||||
|
"row_index": 0,
|
||||||
|
"raw_condition_id": 2313,
|
||||||
|
"subtype": 0,
|
||||||
|
"flag_bytes": [40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
"candidate_name": null,
|
||||||
|
"comparator": "ge",
|
||||||
|
"metric": "Territory Track Pieces",
|
||||||
|
"semantic_family": "numeric_threshold",
|
||||||
|
"semantic_preview": "Test Territory Track Pieces >= 40",
|
||||||
|
"requires_candidate_name_binding": false,
|
||||||
|
"notes": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"grouped_effect_row_counts": [1, 0, 0, 0],
|
||||||
|
"grouped_effect_rows": [
|
||||||
|
{
|
||||||
|
"group_index": 0,
|
||||||
|
"row_index": 0,
|
||||||
|
"descriptor_id": 2,
|
||||||
|
"descriptor_label": "Company Cash",
|
||||||
|
"target_mask_bits": 1,
|
||||||
|
"parameter_family": "company_finance_scalar",
|
||||||
|
"opcode": 8,
|
||||||
|
"raw_scalar_value": 444,
|
||||||
|
"value_byte_0x09": 1,
|
||||||
|
"value_dword_0x0d": 12,
|
||||||
|
"value_byte_0x11": 2,
|
||||||
|
"value_byte_0x12": 3,
|
||||||
|
"value_word_0x14": 24,
|
||||||
|
"value_word_0x16": 36,
|
||||||
|
"row_shape": "multivalue_scalar",
|
||||||
|
"semantic_family": "multivalue_scalar",
|
||||||
|
"semantic_preview": "Set Company Cash to 444 with aux [2, 3, 24, 36]",
|
||||||
|
"locomotive_name": null,
|
||||||
|
"notes": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"decoded_conditions": [
|
||||||
|
{
|
||||||
|
"kind": "territory_numeric_threshold",
|
||||||
|
"metric": "track_pieces_total",
|
||||||
|
"comparator": "ge",
|
||||||
|
"value": 40
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"decoded_actions": [
|
||||||
|
{
|
||||||
|
"kind": "set_company_cash",
|
||||||
|
"target": {
|
||||||
|
"kind": "selected_company"
|
||||||
|
},
|
||||||
|
"value": 444
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"executable_import_ready": true,
|
||||||
|
"notes": [
|
||||||
|
"decoded from grounded real 0x4e9a row framing",
|
||||||
|
"aggregate territory thresholds execute only when overlay territory context is available"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notes": [
|
||||||
|
"real ordinary aggregate territory-track threshold sample"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue