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
|
||||
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
|
||||
negative-sentinel `raw_condition_id = -1` company scopes, while ordinary condition-id semantics and
|
||||
player/territory runtime ownership 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.
|
||||
negative-sentinel `raw_condition_id = -1` company scopes, and the first ordinary nonnegative
|
||||
condition 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. 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
|
||||
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ mod tests {
|
|||
CalendarPoint, OVERLAY_IMPORT_DOCUMENT_FORMAT_VERSION, RuntimeOverlayImportDocument,
|
||||
RuntimeOverlayImportDocumentSource, RuntimeSaveProfileState, RuntimeSaveSliceDocument,
|
||||
RuntimeSaveSliceDocumentSource, RuntimeServiceState, RuntimeSnapshotDocument,
|
||||
RuntimeSnapshotSource, RuntimeState, RuntimeWorldRestoreState,
|
||||
RuntimeSnapshotSource, RuntimeState, RuntimeTrackPieceCounts, RuntimeWorldRestoreState,
|
||||
SAVE_SLICE_DOCUMENT_FORMAT_VERSION, SNAPSHOT_FORMAT_VERSION,
|
||||
save_runtime_overlay_import_document, save_runtime_save_slice_document,
|
||||
save_runtime_snapshot_document,
|
||||
|
|
@ -174,6 +174,8 @@ mod tests {
|
|||
metadata: BTreeMap::new(),
|
||||
companies: Vec::new(),
|
||||
selected_company_id: None,
|
||||
territories: Vec::new(),
|
||||
company_territory_track_piece_counts: Vec::new(),
|
||||
packed_event_collection: None,
|
||||
event_runtime_records: Vec::new(),
|
||||
candidate_availability: BTreeMap::new(),
|
||||
|
|
@ -330,10 +332,15 @@ mod tests {
|
|||
controller_kind: rrt_runtime::RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 100,
|
||||
debt: 0,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
}],
|
||||
selected_company_id: Some(42),
|
||||
territories: Vec::new(),
|
||||
company_territory_track_piece_counts: Vec::new(),
|
||||
packed_event_collection: None,
|
||||
event_runtime_records: Vec::new(),
|
||||
candidate_availability: BTreeMap::new(),
|
||||
|
|
@ -391,6 +398,7 @@ mod tests {
|
|||
negative_sentinel_scope: None,
|
||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||
grouped_effect_rows: vec![],
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![rrt_runtime::RuntimeEffect::AdjustCompanyCash {
|
||||
target: rrt_runtime::RuntimeCompanyTarget::Ids { ids: vec![42] },
|
||||
delta: 25,
|
||||
|
|
|
|||
|
|
@ -64,6 +64,10 @@ pub struct ExpectedRuntimeSummary {
|
|||
#[serde(default)]
|
||||
pub active_company_count: Option<usize>,
|
||||
#[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>,
|
||||
#[serde(default)]
|
||||
pub packed_event_record_count: Option<usize>,
|
||||
|
|
@ -90,6 +94,12 @@ pub struct ExpectedRuntimeSummary {
|
|||
#[serde(default)]
|
||||
pub packed_event_blocked_territory_condition_scope_count: Option<usize>,
|
||||
#[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>,
|
||||
#[serde(default)]
|
||||
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 actual.packed_event_collection_present != present {
|
||||
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 actual.packed_event_blocked_missing_compact_control_count != count {
|
||||
mismatches.push(format!(
|
||||
|
|
|
|||
|
|
@ -6,12 +6,13 @@ use serde::{Deserialize, Serialize};
|
|||
use crate::persistence::{load_runtime_snapshot_document, validate_runtime_snapshot_document};
|
||||
use crate::{
|
||||
CalendarPoint, RuntimeCompanyConditionTestScope, RuntimeCompanyControllerKind,
|
||||
RuntimeCompanyTarget, RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate,
|
||||
RuntimePackedEventCollectionSummary, RuntimePackedEventCompactControlSummary,
|
||||
RuntimePackedEventConditionRowSummary, RuntimePackedEventGroupedEffectRowSummary,
|
||||
RuntimePackedEventNegativeSentinelScopeSummary, RuntimePackedEventRecordSummary,
|
||||
RuntimePackedEventTextBandSummary, RuntimePlayerConditionTestScope, RuntimeSaveProfileState,
|
||||
RuntimeServiceState, RuntimeState, RuntimeWorldRestoreState,
|
||||
RuntimeCompanyTarget, RuntimeCondition, RuntimeEffect, RuntimeEventRecord,
|
||||
RuntimeEventRecordTemplate, RuntimePackedEventCollectionSummary,
|
||||
RuntimePackedEventCompactControlSummary, RuntimePackedEventConditionRowSummary,
|
||||
RuntimePackedEventGroupedEffectRowSummary, RuntimePackedEventNegativeSentinelScopeSummary,
|
||||
RuntimePackedEventRecordSummary, RuntimePackedEventTextBandSummary,
|
||||
RuntimePlayerConditionTestScope, RuntimeSaveProfileState, RuntimeServiceState, RuntimeState,
|
||||
RuntimeWorldRestoreState,
|
||||
SmpLoadedPackedEventNegativeSentinelScopeSummary, SmpLoadedPackedEventRecordSummary,
|
||||
SmpLoadedPackedEventTextBandSummary, SmpLoadedSaveSlice,
|
||||
};
|
||||
|
|
@ -106,6 +107,7 @@ struct ImportCompanyContext {
|
|||
known_company_ids: BTreeSet<u32>,
|
||||
selected_company_id: Option<u32>,
|
||||
has_complete_controller_context: bool,
|
||||
has_territory_context: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
|
@ -117,6 +119,9 @@ enum CompanyTargetImportBlocker {
|
|||
CompanyConditionScopeDisabled,
|
||||
PlayerConditionScope,
|
||||
TerritoryConditionScope,
|
||||
MissingTerritoryContext,
|
||||
NamedTerritoryBinding,
|
||||
UnmappedOrdinaryCondition,
|
||||
}
|
||||
|
||||
impl ImportCompanyContext {
|
||||
|
|
@ -125,6 +130,7 @@ impl ImportCompanyContext {
|
|||
known_company_ids: BTreeSet::new(),
|
||||
selected_company_id: None,
|
||||
has_complete_controller_context: false,
|
||||
has_territory_context: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -140,6 +146,7 @@ impl ImportCompanyContext {
|
|||
&& state.companies.iter().all(|company| {
|
||||
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,
|
||||
companies: Vec::new(),
|
||||
selected_company_id: None,
|
||||
territories: Vec::new(),
|
||||
company_territory_track_piece_counts: Vec::new(),
|
||||
packed_event_collection: projection.packed_event_collection,
|
||||
event_runtime_records: projection.event_runtime_records,
|
||||
candidate_availability: projection.candidate_availability,
|
||||
|
|
@ -220,6 +229,10 @@ pub fn project_save_slice_overlay_to_runtime_state_import(
|
|||
metadata,
|
||||
companies: base_state.companies.clone(),
|
||||
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,
|
||||
event_runtime_records: projection.event_runtime_records,
|
||||
candidate_availability: projection.candidate_availability,
|
||||
|
|
@ -597,8 +610,10 @@ fn runtime_packed_event_record_summary_from_smp(
|
|||
company_context: &ImportCompanyContext,
|
||||
imported: bool,
|
||||
) -> RuntimePackedEventRecordSummary {
|
||||
let lowered_decoded_actions =
|
||||
lowered_record_decoded_actions(record).unwrap_or_else(|_| record.decoded_actions.clone());
|
||||
let lowered_decoded_conditions = lowered_record_decoded_conditions(record, company_context)
|
||||
.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 {
|
||||
record_index: record.record_index,
|
||||
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)
|
||||
.collect(),
|
||||
grouped_company_targets: classify_real_grouped_company_targets(record),
|
||||
decoded_conditions: lowered_decoded_conditions,
|
||||
decoded_actions: lowered_decoded_actions,
|
||||
executable_import_ready: record.executable_import_ready,
|
||||
import_outcome: Some(determine_packed_event_import_outcome(
|
||||
|
|
@ -695,6 +711,11 @@ fn runtime_packed_event_condition_row_summary_from_smp(
|
|||
subtype: row.subtype,
|
||||
flag_bytes: row.flag_bytes.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(),
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
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,
|
||||
Err(_) => return None,
|
||||
};
|
||||
|
|
@ -763,19 +792,42 @@ fn smp_packed_record_to_runtime_event_record(
|
|||
active,
|
||||
marks_collection_dirty,
|
||||
one_shot,
|
||||
conditions: lowered_conditions,
|
||||
effects,
|
||||
}
|
||||
.into_runtime_record())
|
||||
})())
|
||||
}
|
||||
|
||||
fn lowered_record_decoded_actions(
|
||||
fn lowered_record_decoded_conditions(
|
||||
record: &SmpLoadedPackedEventRecordSummary,
|
||||
) -> Result<Vec<RuntimeEffect>, CompanyTargetImportBlocker> {
|
||||
if let Some(blocker) = packed_record_condition_scope_import_blocker(record) {
|
||||
company_context: &ImportCompanyContext,
|
||||
) -> Result<Vec<RuntimeCondition>, CompanyTargetImportBlocker> {
|
||||
if let Some(blocker) = packed_record_condition_scope_import_blocker(record, company_context) {
|
||||
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 {
|
||||
return Ok(record.decoded_actions.clone());
|
||||
};
|
||||
|
|
@ -788,20 +840,53 @@ fn lowered_record_decoded_actions(
|
|||
|
||||
fn packed_record_condition_scope_import_blocker(
|
||||
record: &SmpLoadedPackedEventRecordSummary,
|
||||
company_context: &ImportCompanyContext,
|
||||
) -> Option<CompanyTargetImportBlocker> {
|
||||
if record.standalone_condition_rows.is_empty() {
|
||||
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
|
||||
.standalone_condition_rows
|
||||
.iter()
|
||||
.filter(|row| row.raw_condition_id == -1)
|
||||
.count();
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -811,10 +896,22 @@ fn packed_record_condition_scope_import_blocker(
|
|||
if scope.player_test_scope != RuntimePlayerConditionTestScope::Disabled {
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -890,6 +987,7 @@ fn lower_condition_true_company_target_in_effect(
|
|||
active: record.active,
|
||||
marks_collection_dirty: record.marks_collection_dirty,
|
||||
one_shot: record.one_shot,
|
||||
conditions: record.conditions.clone(),
|
||||
effects: record
|
||||
.effects
|
||||
.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(
|
||||
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(
|
||||
effects: &[RuntimeEffect],
|
||||
company_context: &ImportCompanyContext,
|
||||
allow_condition_true_company: bool,
|
||||
) -> Result<Vec<RuntimeEffect>, String> {
|
||||
effects
|
||||
.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()
|
||||
}
|
||||
|
||||
fn smp_runtime_effect_to_runtime_effect(
|
||||
effect: &RuntimeEffect,
|
||||
company_context: &ImportCompanyContext,
|
||||
allow_condition_true_company: bool,
|
||||
) -> Result<RuntimeEffect, String> {
|
||||
match effect {
|
||||
RuntimeEffect::SetWorldFlag { key, value } => Ok(RuntimeEffect::SetWorldFlag {
|
||||
|
|
@ -943,7 +1088,11 @@ fn smp_runtime_effect_to_runtime_effect(
|
|||
value: *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 {
|
||||
target: target.clone(),
|
||||
value: *value,
|
||||
|
|
@ -953,7 +1102,11 @@ fn smp_runtime_effect_to_runtime_effect(
|
|||
}
|
||||
}
|
||||
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 {
|
||||
target: target.clone(),
|
||||
})
|
||||
|
|
@ -962,7 +1115,11 @@ fn smp_runtime_effect_to_runtime_effect(
|
|||
}
|
||||
}
|
||||
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 {
|
||||
target: target.clone(),
|
||||
value: *value,
|
||||
|
|
@ -972,7 +1129,11 @@ fn smp_runtime_effect_to_runtime_effect(
|
|||
}
|
||||
}
|
||||
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 {
|
||||
target: target.clone(),
|
||||
delta: *delta,
|
||||
|
|
@ -982,7 +1143,11 @@ fn smp_runtime_effect_to_runtime_effect(
|
|||
}
|
||||
}
|
||||
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 {
|
||||
target: target.clone(),
|
||||
delta: *delta,
|
||||
|
|
@ -1007,6 +1172,7 @@ fn smp_runtime_effect_to_runtime_effect(
|
|||
record: Box::new(smp_runtime_record_template_to_runtime(
|
||||
record,
|
||||
company_context,
|
||||
allow_condition_true_company,
|
||||
)?),
|
||||
}),
|
||||
RuntimeEffect::ActivateEventRecord { record_id } => {
|
||||
|
|
@ -1028,6 +1194,7 @@ fn smp_runtime_effect_to_runtime_effect(
|
|||
fn smp_runtime_record_template_to_runtime(
|
||||
record: &RuntimeEventRecordTemplate,
|
||||
company_context: &ImportCompanyContext,
|
||||
allow_condition_true_company: bool,
|
||||
) -> Result<RuntimeEventRecordTemplate, String> {
|
||||
Ok(RuntimeEventRecordTemplate {
|
||||
record_id: record.record_id,
|
||||
|
|
@ -1035,7 +1202,39 @@ fn smp_runtime_record_template_to_runtime(
|
|||
active: record.active,
|
||||
marks_collection_dirty: record.marks_collection_dirty,
|
||||
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"
|
||||
.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(),
|
||||
}
|
||||
}
|
||||
|
|
@ -1125,9 +1333,18 @@ fn determine_packed_event_import_outcome(
|
|||
return "blocked_missing_compact_control".to_string();
|
||||
}
|
||||
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();
|
||||
}
|
||||
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,
|
||||
company_context: &ImportCompanyContext,
|
||||
) -> 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,
|
||||
Err(blocker) => return Some(blocker),
|
||||
};
|
||||
lowered_effects
|
||||
.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(
|
||||
effect: &RuntimeEffect,
|
||||
company_context: &ImportCompanyContext,
|
||||
allow_condition_true_company: bool,
|
||||
) -> Option<CompanyTargetImportBlocker> {
|
||||
match effect {
|
||||
RuntimeEffect::SetCompanyCash { target, .. }
|
||||
|
|
@ -1165,10 +1451,19 @@ fn runtime_effect_company_target_import_blocker(
|
|||
| RuntimeEffect::SetCompanyTrackLayingCapacity { target, .. }
|
||||
| RuntimeEffect::AdjustCompanyCash { target, .. }
|
||||
| RuntimeEffect::AdjustCompanyDebt { target, .. } => {
|
||||
if allow_condition_true_company
|
||||
&& matches!(target, RuntimeCompanyTarget::ConditionTrueCompany)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
company_target_import_blocker(target, company_context)
|
||||
}
|
||||
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::SetCandidateAvailability { .. }
|
||||
|
|
@ -1226,6 +1521,11 @@ fn company_target_import_outcome(blocker: CompanyTargetImportBlocker) -> &'stati
|
|||
}
|
||||
CompanyTargetImportBlocker::PlayerConditionScope => "blocked_player_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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{StepCommand, execute_step_command};
|
||||
use crate::{RuntimeTrackPieceCounts, StepCommand, execute_step_command};
|
||||
|
||||
fn state() -> RuntimeState {
|
||||
RuntimeState {
|
||||
|
|
@ -1517,6 +1817,8 @@ mod tests {
|
|||
metadata: BTreeMap::new(),
|
||||
companies: Vec::new(),
|
||||
selected_company_id: None,
|
||||
territories: Vec::new(),
|
||||
company_territory_track_piece_counts: Vec::new(),
|
||||
packed_event_collection: None,
|
||||
event_runtime_records: Vec::new(),
|
||||
candidate_availability: BTreeMap::new(),
|
||||
|
|
@ -1573,6 +1875,11 @@ mod tests {
|
|||
subtype: 4,
|
||||
flag_bytes: vec![0x30; 25],
|
||||
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()],
|
||||
}]
|
||||
}
|
||||
|
|
@ -1600,6 +1907,7 @@ mod tests {
|
|||
negative_sentinel_scope: None,
|
||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||
grouped_effect_rows: vec![],
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![effect],
|
||||
executable_import_ready: false,
|
||||
notes: vec!["synthetic test record".to_string()],
|
||||
|
|
@ -1996,6 +2304,7 @@ mod tests {
|
|||
negative_sentinel_scope: None,
|
||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||
grouped_effect_rows: Vec::new(),
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: Vec::new(),
|
||||
executable_import_ready: false,
|
||||
notes: vec!["test".to_string()],
|
||||
|
|
@ -2018,6 +2327,7 @@ mod tests {
|
|||
negative_sentinel_scope: None,
|
||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||
grouped_effect_rows: Vec::new(),
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: Vec::new(),
|
||||
executable_import_ready: false,
|
||||
notes: vec!["test".to_string()],
|
||||
|
|
@ -2040,6 +2350,7 @@ mod tests {
|
|||
negative_sentinel_scope: None,
|
||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||
grouped_effect_rows: Vec::new(),
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: Vec::new(),
|
||||
executable_import_ready: false,
|
||||
notes: vec!["test".to_string()],
|
||||
|
|
@ -2252,6 +2563,7 @@ mod tests {
|
|||
negative_sentinel_scope: None,
|
||||
grouped_effect_row_counts: vec![0, 1, 0, 0],
|
||||
grouped_effect_rows: vec![],
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![
|
||||
RuntimeEffect::SetWorldFlag {
|
||||
key: "from_packed_root".to_string(),
|
||||
|
|
@ -2264,6 +2576,7 @@ mod tests {
|
|||
active: true,
|
||||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![RuntimeEffect::SetSpecialCondition {
|
||||
label: "Imported Follow-On".to_string(),
|
||||
value: 1,
|
||||
|
|
@ -2363,6 +2676,7 @@ mod tests {
|
|||
negative_sentinel_scope: None,
|
||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||
grouped_effect_rows: vec![],
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![RuntimeEffect::AdjustCompanyCash {
|
||||
target: crate::RuntimeCompanyTarget::Ids { ids: vec![42] },
|
||||
delta: 50,
|
||||
|
|
@ -2498,6 +2812,9 @@ mod tests {
|
|||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 100,
|
||||
debt: 10,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
},
|
||||
|
|
@ -2506,6 +2823,9 @@ mod tests {
|
|||
controller_kind: RuntimeCompanyControllerKind::Ai,
|
||||
current_cash: 50,
|
||||
debt: 20,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
},
|
||||
|
|
@ -2640,6 +2960,7 @@ mod tests {
|
|||
)),
|
||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: real_grouped_rows(),
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
||||
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||
value: 7,
|
||||
|
|
@ -2694,6 +3015,9 @@ mod tests {
|
|||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 100,
|
||||
debt: 10,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
},
|
||||
|
|
@ -2702,6 +3026,9 @@ mod tests {
|
|||
controller_kind: RuntimeCompanyControllerKind::Ai,
|
||||
current_cash: 50,
|
||||
debt: 20,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
},
|
||||
|
|
@ -2710,6 +3037,9 @@ mod tests {
|
|||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 70,
|
||||
debt: 30,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
},
|
||||
|
|
@ -2763,6 +3093,7 @@ mod tests {
|
|||
)),
|
||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: real_grouped_rows(),
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
||||
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||
value: 7,
|
||||
|
|
@ -2790,6 +3121,7 @@ mod tests {
|
|||
)),
|
||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: real_grouped_rows(),
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
||||
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||
value: 8,
|
||||
|
|
@ -2817,6 +3149,7 @@ mod tests {
|
|||
)),
|
||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: real_grouped_rows(),
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
||||
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||
value: 9,
|
||||
|
|
@ -2844,6 +3177,7 @@ mod tests {
|
|||
)),
|
||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: real_grouped_rows(),
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
||||
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||
value: 10,
|
||||
|
|
@ -2871,6 +3205,7 @@ mod tests {
|
|||
)),
|
||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: real_grouped_rows(),
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
||||
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||
value: 11,
|
||||
|
|
@ -2995,6 +3330,7 @@ mod tests {
|
|||
negative_sentinel_scope: Some(player_negative_sentinel_scope()),
|
||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: real_grouped_rows(),
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
||||
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||
value: 7,
|
||||
|
|
@ -3069,6 +3405,7 @@ mod tests {
|
|||
negative_sentinel_scope: Some(territory_negative_sentinel_scope()),
|
||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: real_grouped_rows(),
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
||||
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||
value: 7,
|
||||
|
|
@ -3143,6 +3480,7 @@ mod tests {
|
|||
negative_sentinel_scope: None,
|
||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: real_grouped_rows(),
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![],
|
||||
executable_import_ready: false,
|
||||
notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()],
|
||||
|
|
@ -3187,10 +3525,15 @@ mod tests {
|
|||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 500,
|
||||
debt: 20,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
}],
|
||||
selected_company_id: Some(42),
|
||||
territories: Vec::new(),
|
||||
company_territory_track_piece_counts: Vec::new(),
|
||||
packed_event_collection: None,
|
||||
event_runtime_records: Vec::new(),
|
||||
candidate_availability: BTreeMap::new(),
|
||||
|
|
@ -3275,6 +3618,7 @@ mod tests {
|
|||
"grouped effect row carries locomotive-name side string".to_string(),
|
||||
],
|
||||
}],
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
||||
target: RuntimeCompanyTarget::SelectedCompany,
|
||||
value: 250,
|
||||
|
|
@ -3324,6 +3668,9 @@ mod tests {
|
|||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 500,
|
||||
debt: 20,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
}],
|
||||
|
|
@ -3384,6 +3731,7 @@ mod tests {
|
|||
negative_sentinel_scope: None,
|
||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: vec![real_deactivate_company_row(true)],
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![RuntimeEffect::DeactivateCompany {
|
||||
target: RuntimeCompanyTarget::SelectedCompany,
|
||||
}],
|
||||
|
|
@ -3469,6 +3817,7 @@ mod tests {
|
|||
negative_sentinel_scope: None,
|
||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: vec![real_deactivate_company_row(false)],
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![],
|
||||
executable_import_ready: false,
|
||||
notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()],
|
||||
|
|
@ -3503,6 +3852,9 @@ mod tests {
|
|||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 500,
|
||||
debt: 20,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
}],
|
||||
|
|
@ -3563,6 +3915,7 @@ mod tests {
|
|||
negative_sentinel_scope: None,
|
||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: vec![real_track_capacity_row(18)],
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![RuntimeEffect::SetCompanyTrackLayingCapacity {
|
||||
target: RuntimeCompanyTarget::SelectedCompany,
|
||||
value: Some(18),
|
||||
|
|
@ -3602,6 +3955,9 @@ mod tests {
|
|||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 500,
|
||||
debt: 20,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
}],
|
||||
|
|
@ -3665,6 +4021,7 @@ mod tests {
|
|||
real_track_capacity_row(18),
|
||||
unsupported_real_grouped_row(),
|
||||
],
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![RuntimeEffect::SetCompanyTrackLayingCapacity {
|
||||
target: RuntimeCompanyTarget::SelectedCompany,
|
||||
value: Some(18),
|
||||
|
|
@ -3713,10 +4070,15 @@ mod tests {
|
|||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 500,
|
||||
debt: 20,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
}],
|
||||
selected_company_id: Some(42),
|
||||
territories: Vec::new(),
|
||||
company_territory_track_piece_counts: Vec::new(),
|
||||
packed_event_collection: None,
|
||||
event_runtime_records: vec![RuntimeEventRecord {
|
||||
record_id: 1,
|
||||
|
|
@ -3726,6 +4088,7 @@ mod tests {
|
|||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![],
|
||||
}],
|
||||
candidate_availability: BTreeMap::new(),
|
||||
|
|
@ -3780,6 +4143,7 @@ mod tests {
|
|||
negative_sentinel_scope: None,
|
||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||
grouped_effect_rows: vec![],
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![RuntimeEffect::AdjustCompanyCash {
|
||||
target: crate::RuntimeCompanyTarget::Ids { ids: vec![42] },
|
||||
delta: 50,
|
||||
|
|
@ -3878,10 +4242,15 @@ mod tests {
|
|||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 100,
|
||||
debt: 0,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
}],
|
||||
selected_company_id: Some(42),
|
||||
territories: Vec::new(),
|
||||
company_territory_track_piece_counts: Vec::new(),
|
||||
packed_event_collection: None,
|
||||
event_runtime_records: Vec::new(),
|
||||
candidate_availability: BTreeMap::new(),
|
||||
|
|
@ -3939,6 +4308,7 @@ mod tests {
|
|||
negative_sentinel_scope: None,
|
||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||
grouped_effect_rows: vec![],
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: vec![RuntimeEffect::AdjustCompanyCash {
|
||||
target: crate::RuntimeCompanyTarget::Ids { ids: vec![42] },
|
||||
delta: 50,
|
||||
|
|
|
|||
|
|
@ -36,12 +36,15 @@ pub use pk4::{
|
|||
};
|
||||
pub use runtime::{
|
||||
RuntimeCompany, RuntimeCompanyConditionTestScope, RuntimeCompanyControllerKind,
|
||||
RuntimeCompanyTarget, RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate,
|
||||
RuntimePackedEventCollectionSummary, RuntimePackedEventCompactControlSummary,
|
||||
RuntimePackedEventConditionRowSummary, RuntimePackedEventGroupedEffectRowSummary,
|
||||
RuntimePackedEventNegativeSentinelScopeSummary, RuntimePackedEventRecordSummary,
|
||||
RuntimePackedEventTextBandSummary, RuntimePlayerConditionTestScope, RuntimeSaveProfileState,
|
||||
RuntimeServiceState, RuntimeState, RuntimeWorldRestoreState,
|
||||
RuntimeCompanyMetric, RuntimeCompanyTarget, RuntimeCompanyTerritoryTrackPieceCount,
|
||||
RuntimeCondition, RuntimeConditionComparator, RuntimeEffect, RuntimeEventRecord,
|
||||
RuntimeEventRecordTemplate, RuntimePackedEventCollectionSummary,
|
||||
RuntimePackedEventCompactControlSummary, RuntimePackedEventConditionRowSummary,
|
||||
RuntimePackedEventGroupedEffectRowSummary, RuntimePackedEventNegativeSentinelScopeSummary,
|
||||
RuntimePackedEventRecordSummary, RuntimePackedEventTextBandSummary,
|
||||
RuntimePlayerConditionTestScope, RuntimeSaveProfileState, RuntimeServiceState, RuntimeState,
|
||||
RuntimeTerritory, RuntimeTerritoryMetric, RuntimeTrackMetric, RuntimeTrackPieceCounts,
|
||||
RuntimeWorldRestoreState,
|
||||
};
|
||||
pub use smp::{
|
||||
SMP_FOUR_SIDECAR_BYTE_PLANES_MIN_BUNDLE_VERSION, SmpAlignedRuntimeRuleBandLane,
|
||||
|
|
|
|||
|
|
@ -94,6 +94,8 @@ mod tests {
|
|||
metadata: BTreeMap::new(),
|
||||
companies: Vec::new(),
|
||||
selected_company_id: None,
|
||||
territories: Vec::new(),
|
||||
company_territory_track_piece_counts: Vec::new(),
|
||||
packed_event_collection: None,
|
||||
event_runtime_records: Vec::new(),
|
||||
candidate_availability: BTreeMap::new(),
|
||||
|
|
|
|||
|
|
@ -22,12 +22,49 @@ pub struct RuntimeCompany {
|
|||
pub company_id: u32,
|
||||
pub current_cash: i64,
|
||||
pub debt: u64,
|
||||
#[serde(default)]
|
||||
pub credit_rating_score: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub prime_rate: Option<i64>,
|
||||
#[serde(default = "runtime_company_default_active")]
|
||||
pub active: bool,
|
||||
#[serde(default)]
|
||||
pub available_track_laying_capacity: Option<u32>,
|
||||
#[serde(default)]
|
||||
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)]
|
||||
|
|
@ -63,6 +100,76 @@ pub enum RuntimePlayerConditionTestScope {
|
|||
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)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
pub enum RuntimeEffect {
|
||||
|
|
@ -121,6 +228,8 @@ pub struct RuntimeEventRecordTemplate {
|
|||
#[serde(default)]
|
||||
pub one_shot: bool,
|
||||
#[serde(default)]
|
||||
pub conditions: Vec<RuntimeCondition>,
|
||||
#[serde(default)]
|
||||
pub effects: Vec<RuntimeEffect>,
|
||||
}
|
||||
|
||||
|
|
@ -138,6 +247,8 @@ pub struct RuntimeEventRecord {
|
|||
#[serde(default)]
|
||||
pub has_fired: bool,
|
||||
#[serde(default)]
|
||||
pub conditions: Vec<RuntimeCondition>,
|
||||
#[serde(default)]
|
||||
pub effects: Vec<RuntimeEffect>,
|
||||
}
|
||||
|
||||
|
|
@ -197,6 +308,8 @@ pub struct RuntimePackedEventRecordSummary {
|
|||
#[serde(default)]
|
||||
pub grouped_company_targets: Vec<Option<RuntimeCompanyTarget>>,
|
||||
#[serde(default)]
|
||||
pub decoded_conditions: Vec<RuntimeCondition>,
|
||||
#[serde(default)]
|
||||
pub decoded_actions: Vec<RuntimeEffect>,
|
||||
#[serde(default)]
|
||||
pub executable_import_ready: bool,
|
||||
|
|
@ -250,6 +363,16 @@ pub struct RuntimePackedEventConditionRowSummary {
|
|||
#[serde(default)]
|
||||
pub candidate_name: Option<String>,
|
||||
#[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>,
|
||||
}
|
||||
|
||||
|
|
@ -293,6 +416,7 @@ impl RuntimeEventRecordTemplate {
|
|||
marks_collection_dirty: self.marks_collection_dirty,
|
||||
one_shot: self.one_shot,
|
||||
has_fired: false,
|
||||
conditions: self.conditions,
|
||||
effects: self.effects,
|
||||
}
|
||||
}
|
||||
|
|
@ -384,6 +508,10 @@ pub struct RuntimeState {
|
|||
#[serde(default)]
|
||||
pub selected_company_id: Option<u32>,
|
||||
#[serde(default)]
|
||||
pub territories: Vec<RuntimeTerritory>,
|
||||
#[serde(default)]
|
||||
pub company_territory_track_piece_counts: Vec<RuntimeCompanyTerritoryTrackPieceCount>,
|
||||
#[serde(default)]
|
||||
pub packed_event_collection: Option<RuntimePackedEventCollectionSummary>,
|
||||
#[serde(default)]
|
||||
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();
|
||||
for record in &self.event_runtime_records {
|
||||
if !seen_record_ids.insert(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() {
|
||||
validate_runtime_effect(effect, &seen_company_ids).map_err(|err| {
|
||||
format!(
|
||||
|
|
@ -613,6 +770,42 @@ impl RuntimeState {
|
|||
"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 {
|
||||
if row.row_shape.trim().is_empty() {
|
||||
|
|
@ -758,6 +951,14 @@ fn validate_event_record_template(
|
|||
record: &RuntimeEventRecordTemplate,
|
||||
valid_company_ids: &BTreeSet<u32>,
|
||||
) -> 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() {
|
||||
validate_runtime_effect(effect, valid_company_ids).map_err(|err| {
|
||||
format!(
|
||||
|
|
@ -770,6 +971,19 @@ fn validate_event_record_template(
|
|||
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(
|
||||
target: &RuntimeCompanyTarget,
|
||||
valid_company_ids: &BTreeSet<u32>,
|
||||
|
|
@ -816,6 +1030,9 @@ mod tests {
|
|||
company_id: 1,
|
||||
current_cash: 100,
|
||||
debt: 0,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||
|
|
@ -824,12 +1041,17 @@ mod tests {
|
|||
company_id: 1,
|
||||
current_cash: 200,
|
||||
debt: 0,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||
},
|
||||
],
|
||||
selected_company_id: None,
|
||||
territories: Vec::new(),
|
||||
company_territory_track_piece_counts: Vec::new(),
|
||||
packed_event_collection: None,
|
||||
event_runtime_records: Vec::new(),
|
||||
candidate_availability: BTreeMap::new(),
|
||||
|
|
@ -877,6 +1099,8 @@ mod tests {
|
|||
metadata: BTreeMap::new(),
|
||||
companies: Vec::new(),
|
||||
selected_company_id: None,
|
||||
territories: Vec::new(),
|
||||
company_territory_track_piece_counts: Vec::new(),
|
||||
packed_event_collection: None,
|
||||
event_runtime_records: Vec::new(),
|
||||
candidate_availability: BTreeMap::new(),
|
||||
|
|
@ -904,11 +1128,16 @@ mod tests {
|
|||
company_id: 1,
|
||||
current_cash: 100,
|
||||
debt: 0,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||
}],
|
||||
selected_company_id: None,
|
||||
territories: Vec::new(),
|
||||
company_territory_track_piece_counts: Vec::new(),
|
||||
packed_event_collection: None,
|
||||
event_runtime_records: vec![RuntimeEventRecord {
|
||||
record_id: 7,
|
||||
|
|
@ -918,6 +1147,7 @@ mod tests {
|
|||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![RuntimeEffect::AdjustCompanyCash {
|
||||
target: RuntimeCompanyTarget::Ids { ids: vec![2] },
|
||||
delta: 50,
|
||||
|
|
@ -948,11 +1178,16 @@ mod tests {
|
|||
company_id: 1,
|
||||
current_cash: 100,
|
||||
debt: 0,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||
}],
|
||||
selected_company_id: None,
|
||||
territories: Vec::new(),
|
||||
company_territory_track_piece_counts: Vec::new(),
|
||||
packed_event_collection: None,
|
||||
event_runtime_records: vec![RuntimeEventRecord {
|
||||
record_id: 7,
|
||||
|
|
@ -962,6 +1197,7 @@ mod tests {
|
|||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![RuntimeEffect::AppendEventRecord {
|
||||
record: Box::new(RuntimeEventRecordTemplate {
|
||||
record_id: 8,
|
||||
|
|
@ -969,6 +1205,7 @@ mod tests {
|
|||
active: true,
|
||||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![RuntimeEffect::AdjustCompanyCash {
|
||||
target: RuntimeCompanyTarget::Ids { ids: vec![2] },
|
||||
delta: 50,
|
||||
|
|
@ -999,6 +1236,8 @@ mod tests {
|
|||
metadata: BTreeMap::new(),
|
||||
companies: Vec::new(),
|
||||
selected_company_id: None,
|
||||
territories: Vec::new(),
|
||||
company_territory_track_piece_counts: Vec::new(),
|
||||
packed_event_collection: Some(RuntimePackedEventCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".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_rows: Vec::new(),
|
||||
grouped_company_targets: Vec::new(),
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: Vec::new(),
|
||||
executable_import_ready: false,
|
||||
import_outcome: None,
|
||||
|
|
@ -1055,6 +1295,7 @@ mod tests {
|
|||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||
grouped_effect_rows: Vec::new(),
|
||||
grouped_company_targets: Vec::new(),
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: Vec::new(),
|
||||
executable_import_ready: false,
|
||||
import_outcome: None,
|
||||
|
|
@ -1088,11 +1329,16 @@ mod tests {
|
|||
company_id: 1,
|
||||
current_cash: 100,
|
||||
debt: 0,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
}],
|
||||
selected_company_id: Some(2),
|
||||
territories: Vec::new(),
|
||||
company_territory_track_piece_counts: Vec::new(),
|
||||
packed_event_collection: None,
|
||||
event_runtime_records: Vec::new(),
|
||||
candidate_availability: BTreeMap::new(),
|
||||
|
|
@ -1120,11 +1366,16 @@ mod tests {
|
|||
company_id: 1,
|
||||
current_cash: 100,
|
||||
debt: 0,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: false,
|
||||
available_track_laying_capacity: None,
|
||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
}],
|
||||
selected_company_id: Some(1),
|
||||
territories: Vec::new(),
|
||||
company_territory_track_piece_counts: Vec::new(),
|
||||
packed_event_collection: None,
|
||||
event_runtime_records: Vec::new(),
|
||||
candidate_availability: BTreeMap::new(),
|
||||
|
|
|
|||
|
|
@ -5,8 +5,9 @@ use serde::{Deserialize, Serialize};
|
|||
use sha2::{Digest, Sha256};
|
||||
|
||||
use crate::{
|
||||
RuntimeCompanyConditionTestScope, RuntimeCompanyTarget, RuntimeEffect,
|
||||
RuntimeEventRecordTemplate, RuntimePlayerConditionTestScope,
|
||||
RuntimeCompanyConditionTestScope, RuntimeCompanyMetric, RuntimeCompanyTarget,
|
||||
RuntimeCondition, RuntimeConditionComparator, RuntimeEffect, RuntimeEventRecordTemplate,
|
||||
RuntimePlayerConditionTestScope, RuntimeTerritoryMetric, RuntimeTrackMetric,
|
||||
};
|
||||
|
||||
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)]
|
||||
struct KnownSpecialConditionDefinition {
|
||||
slot_index: u8,
|
||||
|
|
@ -1321,6 +1449,8 @@ pub struct SmpLoadedPackedEventRecordSummary {
|
|||
#[serde(default)]
|
||||
pub grouped_effect_rows: Vec<SmpLoadedPackedEventGroupedEffectRowSummary>,
|
||||
#[serde(default)]
|
||||
pub decoded_conditions: Vec<RuntimeCondition>,
|
||||
#[serde(default)]
|
||||
pub decoded_actions: Vec<RuntimeEffect>,
|
||||
#[serde(default)]
|
||||
pub executable_import_ready: bool,
|
||||
|
|
@ -1369,6 +1499,16 @@ pub struct SmpLoadedPackedEventConditionRowSummary {
|
|||
#[serde(default)]
|
||||
pub candidate_name: Option<String>,
|
||||
#[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>,
|
||||
}
|
||||
|
||||
|
|
@ -1853,6 +1993,7 @@ fn parse_synthetic_event_runtime_record_summary(
|
|||
negative_sentinel_scope: None,
|
||||
grouped_effect_row_counts,
|
||||
grouped_effect_rows: Vec::new(),
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions,
|
||||
executable_import_ready,
|
||||
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| {
|
||||
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
|
||||
.as_ref()
|
||||
.map(|control| decode_real_grouped_effect_actions(&grouped_effect_rows, control))
|
||||
.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()
|
||||
&& decoded_actions.len() == grouped_effect_rows.len()
|
||||
&& decoded_conditions.len() == ordinary_condition_row_count
|
||||
&& decoded_actions
|
||||
.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;
|
||||
Some((
|
||||
SmpLoadedPackedEventRecordSummary {
|
||||
|
|
@ -1989,6 +2142,7 @@ fn parse_real_event_runtime_record_summary(
|
|||
negative_sentinel_scope,
|
||||
grouped_effect_row_counts,
|
||||
grouped_effect_rows,
|
||||
decoded_conditions,
|
||||
decoded_actions,
|
||||
executable_import_ready,
|
||||
notes: vec![
|
||||
|
|
@ -2074,6 +2228,22 @@ fn parse_real_condition_row_summary(
|
|||
) -> Option<SmpLoadedPackedEventConditionRowSummary> {
|
||||
let raw_condition_id = read_u32_at(row_bytes, 0)? as i32;
|
||||
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();
|
||||
if raw_condition_id < 0 {
|
||||
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() {
|
||||
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 {
|
||||
row_index,
|
||||
raw_condition_id,
|
||||
subtype,
|
||||
flag_bytes: row_bytes
|
||||
.get(5..PACKED_EVENT_REAL_CONDITION_ROW_LEN)?
|
||||
.to_vec(),
|
||||
flag_bytes,
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
|
@ -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(
|
||||
row_bytes: &[u8],
|
||||
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(
|
||||
descriptor_id: u32,
|
||||
) -> Option<RealGroupedEffectDescriptorMetadata> {
|
||||
|
|
@ -2446,6 +2725,7 @@ fn parse_synthetic_event_runtime_record_template(
|
|||
active: flags & 0x01 != 0,
|
||||
marks_collection_dirty: flags & 0x02 != 0,
|
||||
one_shot: flags & 0x04 != 0,
|
||||
conditions: Vec::new(),
|
||||
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(
|
||||
live_entry_ids: &[u32],
|
||||
note: &str,
|
||||
|
|
@ -2549,6 +2837,7 @@ fn build_unsupported_event_runtime_record_summaries(
|
|||
negative_sentinel_scope: None,
|
||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||
grouped_effect_rows: Vec::new(),
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: Vec::new(),
|
||||
executable_import_ready: false,
|
||||
notes: vec![note.to_string()],
|
||||
|
|
@ -7830,6 +8119,11 @@ mod tests {
|
|||
subtype: 4,
|
||||
flag_bytes: vec![0x30; 25],
|
||||
candidate_name: Some("AutoPlant".to_string()),
|
||||
comparator: None,
|
||||
metric: None,
|
||||
semantic_family: None,
|
||||
semantic_preview: None,
|
||||
requires_candidate_name_binding: false,
|
||||
notes: vec![],
|
||||
}];
|
||||
let summary = derive_negative_sentinel_scope_summary(
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@ use std::collections::BTreeSet;
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
RuntimeCompanyControllerKind, RuntimeCompanyTarget, RuntimeEffect, RuntimeEventRecordTemplate,
|
||||
RuntimeState, RuntimeSummary, calendar::BoundaryEventKind,
|
||||
RuntimeCompanyControllerKind, RuntimeCompanyMetric, RuntimeCompanyTarget, RuntimeCondition,
|
||||
RuntimeConditionComparator, RuntimeEffect, RuntimeEventRecordTemplate, RuntimeState,
|
||||
RuntimeSummary, RuntimeTerritoryMetric, RuntimeTrackMetric, RuntimeTrackPieceCounts,
|
||||
calendar::BoundaryEventKind,
|
||||
};
|
||||
|
||||
const PERIODIC_TRIGGER_KIND_ORDER: [u8; 6] = [1, 0, 3, 2, 5, 4];
|
||||
|
|
@ -79,6 +81,11 @@ struct AppliedEffectsSummary {
|
|||
removed_record_ids: Vec<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct ResolvedConditionContext {
|
||||
matching_company_ids: BTreeSet<u32>,
|
||||
}
|
||||
|
||||
pub fn execute_step_command(
|
||||
state: &mut RuntimeState,
|
||||
command: &StepCommand,
|
||||
|
|
@ -212,19 +219,31 @@ fn service_trigger_kind(
|
|||
.or_insert(0) += 1;
|
||||
|
||||
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];
|
||||
(
|
||||
record.record_id,
|
||||
record.conditions.clone(),
|
||||
record.effects.clone(),
|
||||
record.marks_collection_dirty,
|
||||
record.one_shot,
|
||||
)
|
||||
};
|
||||
|
||||
let Some(condition_context) = evaluate_record_conditions(state, &record_conditions)? else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let effect_summary = apply_runtime_effects(
|
||||
state,
|
||||
&record_effects,
|
||||
&condition_context,
|
||||
&mut mutated_company_ids,
|
||||
&mut staged_event_graph_mutations,
|
||||
)?;
|
||||
|
|
@ -275,6 +294,7 @@ fn service_trigger_kind(
|
|||
fn apply_runtime_effects(
|
||||
state: &mut RuntimeState,
|
||||
effects: &[RuntimeEffect],
|
||||
condition_context: &ResolvedConditionContext,
|
||||
mutated_company_ids: &mut BTreeSet<u32>,
|
||||
staged_event_graph_mutations: &mut Vec<EventGraphMutation>,
|
||||
) -> Result<AppliedEffectsSummary, String> {
|
||||
|
|
@ -286,7 +306,7 @@ fn apply_runtime_effects(
|
|||
state.world_flags.insert(key.clone(), *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 {
|
||||
let company = state
|
||||
.companies
|
||||
|
|
@ -300,7 +320,7 @@ fn apply_runtime_effects(
|
|||
}
|
||||
}
|
||||
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 {
|
||||
let company = state
|
||||
.companies
|
||||
|
|
@ -319,7 +339,7 @@ fn apply_runtime_effects(
|
|||
}
|
||||
}
|
||||
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 {
|
||||
let company = state
|
||||
.companies
|
||||
|
|
@ -335,7 +355,7 @@ fn apply_runtime_effects(
|
|||
}
|
||||
}
|
||||
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 {
|
||||
let company = state
|
||||
.companies
|
||||
|
|
@ -352,7 +372,7 @@ fn apply_runtime_effects(
|
|||
}
|
||||
}
|
||||
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 {
|
||||
let company = state
|
||||
.companies
|
||||
|
|
@ -456,9 +476,114 @@ fn commit_staged_event_graph_mutations(
|
|||
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(
|
||||
state: &RuntimeState,
|
||||
target: &RuntimeCompanyTarget,
|
||||
condition_context: &ResolvedConditionContext,
|
||||
) -> Result<Vec<u32>, String> {
|
||||
match target {
|
||||
RuntimeCompanyTarget::AllActive => Ok(state
|
||||
|
|
@ -538,11 +663,101 @@ fn resolve_company_target_ids(
|
|||
}
|
||||
}
|
||||
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> {
|
||||
if delta >= 0 {
|
||||
current
|
||||
|
|
@ -583,10 +798,15 @@ mod tests {
|
|||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||
current_cash: 10,
|
||||
debt: 0,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
}],
|
||||
selected_company_id: None,
|
||||
territories: Vec::new(),
|
||||
company_territory_track_piece_counts: Vec::new(),
|
||||
packed_event_collection: None,
|
||||
event_runtime_records: Vec::new(),
|
||||
candidate_availability: BTreeMap::new(),
|
||||
|
|
@ -647,6 +867,7 @@ mod tests {
|
|||
marks_collection_dirty: true,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![RuntimeEffect::SetWorldFlag {
|
||||
key: "runtime.effect_fired".to_string(),
|
||||
value: true,
|
||||
|
|
@ -660,6 +881,7 @@ mod tests {
|
|||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![RuntimeEffect::AdjustCompanyCash {
|
||||
target: RuntimeCompanyTarget::AllActive,
|
||||
delta: 5,
|
||||
|
|
@ -673,6 +895,7 @@ mod tests {
|
|||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![RuntimeEffect::SetSpecialCondition {
|
||||
label: "Dirty rerun fired".to_string(),
|
||||
value: 1,
|
||||
|
|
@ -747,6 +970,9 @@ mod tests {
|
|||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||
current_cash: 10,
|
||||
debt: 5,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
},
|
||||
|
|
@ -755,6 +981,9 @@ mod tests {
|
|||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||
current_cash: 20,
|
||||
debt: 8,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
},
|
||||
|
|
@ -767,6 +996,7 @@ mod tests {
|
|||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![
|
||||
RuntimeEffect::AdjustCompanyCash {
|
||||
target: RuntimeCompanyTarget::Ids { ids: vec![2] },
|
||||
|
|
@ -803,6 +1033,9 @@ mod tests {
|
|||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 10,
|
||||
debt: 0,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
},
|
||||
|
|
@ -811,6 +1044,9 @@ mod tests {
|
|||
controller_kind: RuntimeCompanyControllerKind::Ai,
|
||||
current_cash: 20,
|
||||
debt: 2,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
},
|
||||
|
|
@ -825,6 +1061,7 @@ mod tests {
|
|||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![RuntimeEffect::AdjustCompanyCash {
|
||||
target: RuntimeCompanyTarget::HumanCompanies,
|
||||
delta: 5,
|
||||
|
|
@ -838,6 +1075,7 @@ mod tests {
|
|||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![RuntimeEffect::AdjustCompanyDebt {
|
||||
target: RuntimeCompanyTarget::AiCompanies,
|
||||
delta: 3,
|
||||
|
|
@ -851,6 +1089,7 @@ mod tests {
|
|||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![RuntimeEffect::AdjustCompanyCash {
|
||||
target: RuntimeCompanyTarget::SelectedCompany,
|
||||
delta: 7,
|
||||
|
|
@ -884,6 +1123,7 @@ mod tests {
|
|||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![RuntimeEffect::AdjustCompanyCash {
|
||||
target: RuntimeCompanyTarget::SelectedCompany,
|
||||
delta: 1,
|
||||
|
|
@ -912,6 +1152,7 @@ mod tests {
|
|||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![RuntimeEffect::AdjustCompanyCash {
|
||||
target: RuntimeCompanyTarget::HumanCompanies,
|
||||
delta: 1,
|
||||
|
|
@ -938,6 +1179,9 @@ mod tests {
|
|||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 10,
|
||||
debt: 1,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
},
|
||||
|
|
@ -946,6 +1190,9 @@ mod tests {
|
|||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 20,
|
||||
debt: 2,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: false,
|
||||
available_track_laying_capacity: None,
|
||||
},
|
||||
|
|
@ -954,6 +1201,9 @@ mod tests {
|
|||
controller_kind: RuntimeCompanyControllerKind::Ai,
|
||||
current_cash: 30,
|
||||
debt: 3,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
},
|
||||
|
|
@ -967,6 +1217,7 @@ mod tests {
|
|||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![RuntimeEffect::AdjustCompanyCash {
|
||||
target: RuntimeCompanyTarget::AllActive,
|
||||
delta: 5,
|
||||
|
|
@ -980,6 +1231,7 @@ mod tests {
|
|||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![RuntimeEffect::AdjustCompanyDebt {
|
||||
target: RuntimeCompanyTarget::HumanCompanies,
|
||||
delta: 4,
|
||||
|
|
@ -993,6 +1245,7 @@ mod tests {
|
|||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![RuntimeEffect::AdjustCompanyDebt {
|
||||
target: RuntimeCompanyTarget::AiCompanies,
|
||||
delta: 6,
|
||||
|
|
@ -1024,6 +1277,9 @@ mod tests {
|
|||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 10,
|
||||
debt: 0,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: Some(8),
|
||||
}],
|
||||
|
|
@ -1036,6 +1292,7 @@ mod tests {
|
|||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![RuntimeEffect::DeactivateCompany {
|
||||
target: RuntimeCompanyTarget::SelectedCompany,
|
||||
}],
|
||||
|
|
@ -1063,6 +1320,9 @@ mod tests {
|
|||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 10,
|
||||
debt: 0,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
},
|
||||
|
|
@ -1071,6 +1331,9 @@ mod tests {
|
|||
controller_kind: RuntimeCompanyControllerKind::Ai,
|
||||
current_cash: 20,
|
||||
debt: 0,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
},
|
||||
|
|
@ -1083,6 +1346,7 @@ mod tests {
|
|||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![RuntimeEffect::SetCompanyTrackLayingCapacity {
|
||||
target: RuntimeCompanyTarget::Ids { ids: vec![2] },
|
||||
value: Some(14),
|
||||
|
|
@ -1113,6 +1377,7 @@ mod tests {
|
|||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![RuntimeEffect::AdjustCompanyCash {
|
||||
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||
delta: 1,
|
||||
|
|
@ -1141,6 +1406,7 @@ mod tests {
|
|||
marks_collection_dirty: false,
|
||||
one_shot: true,
|
||||
has_fired: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![RuntimeEffect::SetWorldFlag {
|
||||
key: "one_shot".to_string(),
|
||||
value: true,
|
||||
|
|
@ -1177,6 +1443,9 @@ mod tests {
|
|||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||
current_cash: 10,
|
||||
debt: 2,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
}],
|
||||
|
|
@ -1188,6 +1457,7 @@ mod tests {
|
|||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![RuntimeEffect::AdjustCompanyDebt {
|
||||
target: RuntimeCompanyTarget::AllActive,
|
||||
delta: -3,
|
||||
|
|
@ -1215,6 +1485,7 @@ mod tests {
|
|||
marks_collection_dirty: false,
|
||||
one_shot: true,
|
||||
has_fired: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![RuntimeEffect::AppendEventRecord {
|
||||
record: Box::new(RuntimeEventRecordTemplate {
|
||||
record_id: 41,
|
||||
|
|
@ -1222,6 +1493,7 @@ mod tests {
|
|||
active: true,
|
||||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![RuntimeEffect::SetWorldFlag {
|
||||
key: "follow_on_later_pass".to_string(),
|
||||
value: true,
|
||||
|
|
@ -1268,6 +1540,7 @@ mod tests {
|
|||
marks_collection_dirty: true,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![RuntimeEffect::AppendEventRecord {
|
||||
record: Box::new(RuntimeEventRecordTemplate {
|
||||
record_id: 51,
|
||||
|
|
@ -1275,6 +1548,7 @@ mod tests {
|
|||
active: true,
|
||||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![RuntimeEffect::SetWorldFlag {
|
||||
key: "dirty_rerun_follow_on".to_string(),
|
||||
value: true,
|
||||
|
|
@ -1314,6 +1588,7 @@ mod tests {
|
|||
marks_collection_dirty: false,
|
||||
one_shot: true,
|
||||
has_fired: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![
|
||||
RuntimeEffect::AppendEventRecord {
|
||||
record: Box::new(RuntimeEventRecordTemplate {
|
||||
|
|
@ -1322,6 +1597,7 @@ mod tests {
|
|||
active: true,
|
||||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![RuntimeEffect::SetCandidateAvailability {
|
||||
name: "Appended Industry".to_string(),
|
||||
value: 1,
|
||||
|
|
@ -1341,6 +1617,7 @@ mod tests {
|
|||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![RuntimeEffect::SetWorldFlag {
|
||||
key: "deactivated_after_first_pass".to_string(),
|
||||
value: true,
|
||||
|
|
@ -1354,6 +1631,7 @@ mod tests {
|
|||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![RuntimeEffect::SetSpecialCondition {
|
||||
label: "Activated On Second Pass".to_string(),
|
||||
value: 1,
|
||||
|
|
@ -1367,6 +1645,7 @@ mod tests {
|
|||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![RuntimeEffect::SetWorldFlag {
|
||||
key: "removed_after_first_pass".to_string(),
|
||||
value: true,
|
||||
|
|
@ -1436,6 +1715,7 @@ mod tests {
|
|||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![RuntimeEffect::AppendEventRecord {
|
||||
record: Box::new(RuntimeEventRecordTemplate {
|
||||
record_id: 71,
|
||||
|
|
@ -1443,6 +1723,7 @@ mod tests {
|
|||
active: true,
|
||||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
conditions: Vec::new(),
|
||||
effects: Vec::new(),
|
||||
}),
|
||||
}],
|
||||
|
|
@ -1455,6 +1736,7 @@ mod tests {
|
|||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: Vec::new(),
|
||||
effects: Vec::new(),
|
||||
},
|
||||
],
|
||||
|
|
@ -1480,6 +1762,7 @@ mod tests {
|
|||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
conditions: Vec::new(),
|
||||
effects: vec![RuntimeEffect::ActivateEventRecord { record_id: 999 }],
|
||||
}],
|
||||
..state()
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ pub struct RuntimeSummary {
|
|||
pub metadata_count: usize,
|
||||
pub 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_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_player_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_unmapped_real_descriptor_count: usize,
|
||||
pub packed_event_blocked_structural_only_count: usize,
|
||||
|
|
@ -131,6 +136,8 @@ impl RuntimeSummary {
|
|||
.iter()
|
||||
.filter(|company| company.active)
|
||||
.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_record_count: state
|
||||
.packed_event_collection
|
||||
|
|
@ -267,6 +274,48 @@ impl RuntimeSummary {
|
|||
.count()
|
||||
})
|
||||
.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_collection
|
||||
.as_ref()
|
||||
|
|
@ -355,6 +404,7 @@ mod tests {
|
|||
use crate::{
|
||||
CalendarPoint, RuntimeCompany, RuntimeCompanyControllerKind,
|
||||
RuntimePackedEventCollectionSummary, RuntimePackedEventRecordSummary,
|
||||
RuntimeTrackPieceCounts,
|
||||
RuntimeSaveProfileState, RuntimeServiceState, RuntimeState, RuntimeWorldRestoreState,
|
||||
};
|
||||
|
||||
|
|
@ -375,6 +425,8 @@ mod tests {
|
|||
metadata: BTreeMap::new(),
|
||||
companies: Vec::new(),
|
||||
selected_company_id: None,
|
||||
territories: Vec::new(),
|
||||
company_territory_track_piece_counts: Vec::new(),
|
||||
packed_event_collection: Some(RuntimePackedEventCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".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_rows: Vec::new(),
|
||||
grouped_company_targets: Vec::new(),
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: Vec::new(),
|
||||
executable_import_ready: false,
|
||||
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_rows: Vec::new(),
|
||||
grouped_company_targets: Vec::new(),
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: Vec::new(),
|
||||
executable_import_ready: false,
|
||||
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_rows: Vec::new(),
|
||||
grouped_company_targets: Vec::new(),
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: Vec::new(),
|
||||
executable_import_ready: false,
|
||||
import_outcome: Some(
|
||||
|
|
@ -481,6 +536,7 @@ mod tests {
|
|||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||
grouped_effect_rows: Vec::new(),
|
||||
grouped_company_targets: Vec::new(),
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: Vec::new(),
|
||||
executable_import_ready: false,
|
||||
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_rows: Vec::new(),
|
||||
grouped_company_targets: Vec::new(),
|
||||
decoded_conditions: Vec::new(),
|
||||
decoded_actions: Vec::new(),
|
||||
executable_import_ready: false,
|
||||
import_outcome: Some("blocked_territory_condition_scope".to_string()),
|
||||
|
|
@ -573,6 +630,9 @@ mod tests {
|
|||
company_id: 1,
|
||||
current_cash: 10,
|
||||
debt: 0,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
|
|
@ -581,12 +641,17 @@ mod tests {
|
|||
company_id: 2,
|
||||
current_cash: 20,
|
||||
debt: 0,
|
||||
credit_rating_score: None,
|
||||
prime_rate: None,
|
||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||
active: false,
|
||||
available_track_laying_capacity: Some(7),
|
||||
controller_kind: RuntimeCompanyControllerKind::Ai,
|
||||
},
|
||||
],
|
||||
selected_company_id: None,
|
||||
territories: Vec::new(),
|
||||
company_territory_track_piece_counts: Vec::new(),
|
||||
packed_event_collection: None,
|
||||
event_runtime_records: Vec::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,
|
||||
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`
|
||||
company scopes; broader ordinary condition-id evaluation and player/territory runtime ownership
|
||||
are the remaining condition frontier, and mixed supported/unsupported real rows stay parity-only
|
||||
company scopes, and the first ordinary nonnegative condition batch now executes too: numeric
|
||||
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,
|
||||
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
|
||||
|
|
|
|||
|
|
@ -35,11 +35,15 @@ Implemented today:
|
|||
`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
|
||||
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
|
||||
ordinary nonnegative condition-id semantics plus runtime ownership for the still-blocked player and
|
||||
territory scope families, alongside broader real grouped-descriptor coverage beyond the current
|
||||
company-scoped batch.
|
||||
broader ordinary condition-id coverage beyond numeric thresholds, plus runtime ownership for the
|
||||
still-blocked player-scoped and named-territory condition families, alongside wider real
|
||||
grouped-descriptor coverage beyond the current company-scoped batch.
|
||||
|
||||
## Why This Boundary
|
||||
|
||||
|
|
@ -236,8 +240,10 @@ Current status:
|
|||
raw `.smp` binaries
|
||||
- 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
|
||||
- the remaining gap is wider real grouped-descriptor semantic coverage plus ordinary condition-id
|
||||
evaluation and player/territory runtime ownership, not first-pass captured-runtime plumbing
|
||||
- aggregate territory context and company-territory track counters now flow through tracked overlay
|
||||
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
|
||||
|
||||
|
|
@ -380,48 +386,52 @@ Checked-in fixture families already include:
|
|||
|
||||
## Next Slice
|
||||
|
||||
The recommended next implementation slice is broader real grouped-descriptor coverage on top of the
|
||||
now-stable compact-control, symbolic-target, and current company-scoped real-family batch.
|
||||
The recommended next implementation slice is broader ordinary-condition breadth on top of the
|
||||
now-stable numeric-threshold, overlay-context, and current company-scoped real-descriptor batch.
|
||||
|
||||
Target behavior:
|
||||
|
||||
- keep descriptors `2` `Company Cash`, `13` `Deactivate Company`, and `16`
|
||||
`Company Track Pieces Buildable` as the proof that real grouped rows can cross the whole path:
|
||||
parse, semantic summary, overlay-backed import, and ordinary trigger execution
|
||||
- recover more real descriptor identities from the checked-in effect table and expose their target
|
||||
masks and conservative semantic previews without guessing unsupported behavior
|
||||
- widen executable real import only when both descriptor identity and runtime effect semantics are
|
||||
grounded enough to map into the normalized runtime path honestly
|
||||
- keep condition-relative company scopes explicit until a real condition evaluator exists, instead
|
||||
of silently degrading or inventing target resolution
|
||||
- preserve the current proof set for real ordinary-condition execution:
|
||||
company finance, company track, aggregate territory track, and company-territory track numeric
|
||||
thresholds all pass through parse, semantic summary, overlay-backed import, and ordinary trigger
|
||||
execution
|
||||
- extend ordinary condition coverage beyond numeric thresholds only when comparator semantics,
|
||||
runtime ownership, and binding rules are grounded enough to lower honestly into the normalized
|
||||
runtime path
|
||||
- keep named-territory ordinary rows explicit and parity-visible until candidate-name territory
|
||||
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:
|
||||
|
||||
- additional checked-in grouped-descriptor metadata entries keyed by recovered descriptor id
|
||||
- more parity summaries with real descriptor labels, target masks, parameter families, and semantic
|
||||
previews
|
||||
- more selective real-row `decoded_actions` only where the descriptor-to-runtime mapping is
|
||||
supported end to end
|
||||
- additional checked-in ordinary-condition metadata entries beyond the current numeric-threshold
|
||||
allowlist
|
||||
- richer runtime ownership for still-blocked condition domains such as named territory and player
|
||||
scope
|
||||
- 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:
|
||||
|
||||
- preserve the parity-heavy tracked sample as the condition-relative blocked frontier while it now
|
||||
carries recovered `Company Cash` semantics with executable import readiness
|
||||
- keep overlay-backed captured fixtures for the executable company-scoped real families:
|
||||
`Company Cash`, `Deactivate Company`, and `Company Track Pieces Buildable`
|
||||
- keep a mixed real-row overlay fixture to lock the all-or-nothing parity rule for partially
|
||||
supported real records
|
||||
- preserve the new ordinary-condition tracked overlays for executable company finance, company
|
||||
track, aggregate territory track, and company-territory track thresholds
|
||||
- preserve the named-territory tracked overlay as the explicit binding blocker frontier
|
||||
- keep the older negative-sentinel, mixed real-row, and company-scoped descriptor fixtures green so
|
||||
ordinary-condition breadth does not regress descriptor-side execution
|
||||
- keep synthetic harness, save-slice, and overlay paths green as the real descriptor surface widens
|
||||
|
||||
Current local constraint:
|
||||
|
||||
- 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
|
||||
tracked JSON artifacts until new captures exist
|
||||
event save set, so wider ordinary-condition and descriptor recovery still needs to rely on the
|
||||
grounded static tables and tracked JSON artifacts until new captures exist
|
||||
|
||||
Do not mix this slice with:
|
||||
|
||||
- shell queue/modal behavior
|
||||
- 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
|
||||
|
|
|
|||
|
|
@ -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