diff --git a/README.md b/README.md index a600a11..217a0b9 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,13 @@ named-territory binding now executes, and the runtime now also carries the minim train roster and opaque economic-status lane needed for real descriptors `8` `Economic Status`, `9` `Confiscate All`, and `15` `Retire Train` to execute through the same path. Descriptor `3` `Territory - Allow All` now executes too, reinterpreted as company-to-territory access rights -rather than a territory-owned policy bit. Shell purchase-flow and selected-profile parity remain -out of scope. 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. +rather than a territory-owned policy bit. Whole-game ordinary-condition execution now exists too: +special-condition thresholds, candidate-availability thresholds, and economic-status-code +thresholds now gate imported runtime records through the same service path, with explicit unmapped +world-condition and world-descriptor frontier buckets where current checked-in metadata still +stops. Shell purchase-flow and selected-profile parity remain out of scope. 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 diff --git a/crates/rrt-fixtures/src/schema.rs b/crates/rrt-fixtures/src/schema.rs index 0e3e2eb..db6872d 100644 --- a/crates/rrt-fixtures/src/schema.rs +++ b/crates/rrt-fixtures/src/schema.rs @@ -118,10 +118,14 @@ pub struct ExpectedRuntimeSummary { #[serde(default)] pub packed_event_blocked_unmapped_ordinary_condition_count: Option, #[serde(default)] + pub packed_event_blocked_unmapped_world_condition_count: Option, + #[serde(default)] pub packed_event_blocked_missing_compact_control_count: Option, #[serde(default)] pub packed_event_blocked_unmapped_real_descriptor_count: Option, #[serde(default)] + pub packed_event_blocked_unmapped_world_descriptor_count: Option, + #[serde(default)] pub packed_event_blocked_territory_access_variant_count: Option, #[serde(default)] pub packed_event_blocked_territory_access_scope_count: Option, @@ -601,6 +605,14 @@ impl ExpectedRuntimeSummary { )); } } + if let Some(count) = self.packed_event_blocked_unmapped_world_condition_count { + if actual.packed_event_blocked_unmapped_world_condition_count != count { + mismatches.push(format!( + "packed_event_blocked_unmapped_world_condition_count mismatch: expected {count}, got {}", + actual.packed_event_blocked_unmapped_world_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!( @@ -617,6 +629,14 @@ impl ExpectedRuntimeSummary { )); } } + if let Some(count) = self.packed_event_blocked_unmapped_world_descriptor_count { + if actual.packed_event_blocked_unmapped_world_descriptor_count != count { + mismatches.push(format!( + "packed_event_blocked_unmapped_world_descriptor_count mismatch: expected {count}, got {}", + actual.packed_event_blocked_unmapped_world_descriptor_count + )); + } + } if let Some(count) = self.packed_event_blocked_territory_access_variant_count { if actual.packed_event_blocked_territory_access_variant_count != count { mismatches.push(format!( diff --git a/crates/rrt-runtime/src/import.rs b/crates/rrt-runtime/src/import.rs index a715292..c68b82d 100644 --- a/crates/rrt-runtime/src/import.rs +++ b/crates/rrt-runtime/src/import.rs @@ -132,6 +132,7 @@ enum ImportBlocker { MissingTerritoryContext, NamedTerritoryBinding, UnmappedOrdinaryCondition, + UnmappedWorldCondition, MissingTrainContext, MissingTrainTerritoryContext, } @@ -923,7 +924,11 @@ fn packed_record_condition_scope_import_blocker( .count(); if ordinary_condition_row_count != 0 { if ordinary_condition_row_count != record.decoded_conditions.len() { - return Some(ImportBlocker::UnmappedOrdinaryCondition); + return Some(if record_has_world_state_condition_rows(record) { + ImportBlocker::UnmappedWorldCondition + } else { + ImportBlocker::UnmappedOrdinaryCondition + }); } if (!company_context.has_territory_context) && (record @@ -1205,6 +1210,30 @@ fn lower_condition_targets_in_condition( comparator: *comparator, value: *value, }, + RuntimeCondition::SpecialConditionThreshold { + label, + comparator, + value, + } => RuntimeCondition::SpecialConditionThreshold { + label: label.clone(), + comparator: *comparator, + value: *value, + }, + RuntimeCondition::CandidateAvailabilityThreshold { + name, + comparator, + value, + } => RuntimeCondition::CandidateAvailabilityThreshold { + name: name.clone(), + comparator: *comparator, + value: *value, + }, + RuntimeCondition::EconomicStatusCodeThreshold { comparator, value } => { + RuntimeCondition::EconomicStatusCodeThreshold { + comparator: *comparator, + value: *value, + } + } }) } @@ -1281,7 +1310,10 @@ fn condition_uses_condition_true_company(condition: &RuntimeCondition) -> bool { | RuntimeCondition::CompanyTerritoryNumericThreshold { target, .. } => { matches!(target, RuntimeCompanyTarget::ConditionTrueCompany) } - RuntimeCondition::TerritoryNumericThreshold { .. } => false, + RuntimeCondition::TerritoryNumericThreshold { .. } + | RuntimeCondition::SpecialConditionThreshold { .. } + | RuntimeCondition::CandidateAvailabilityThreshold { .. } + | RuntimeCondition::EconomicStatusCodeThreshold { .. } => false, } } @@ -1351,8 +1383,11 @@ fn smp_runtime_effect_to_runtime_effect( territory, value, } => { - if !company_target_allowed_for_import(target, company_context, allow_condition_true_company) - { + if !company_target_allowed_for_import( + target, + company_context, + allow_condition_true_company, + ) { Err(company_target_import_error_message(target, company_context)) } else if territory_target_import_blocker(territory, company_context).is_some() { Err("packed effect requires territory runtime context".to_string()) @@ -1657,6 +1692,9 @@ fn company_target_import_error_message( Some(ImportBlocker::UnmappedOrdinaryCondition) => { "packed ordinary condition is not yet mapped".to_string() } + Some(ImportBlocker::UnmappedWorldCondition) => { + "packed whole-game condition is not yet mapped".to_string() + } Some(ImportBlocker::MissingTrainContext) => { "packed effect requires runtime train context".to_string() } @@ -1788,12 +1826,23 @@ fn determine_packed_event_import_outcome( { return "blocked_retire_train_variant".to_string(); } + if record + .grouped_effect_rows + .iter() + .any(real_grouped_row_is_world_state_family) + { + return "blocked_unmapped_world_descriptor".to_string(); + } return if record .standalone_condition_rows .iter() .any(|row| row.raw_condition_id >= 0) { - "blocked_unmapped_ordinary_condition".to_string() + if record_has_world_state_condition_rows(record) { + "blocked_unmapped_world_condition".to_string() + } else { + "blocked_unmapped_ordinary_condition".to_string() + } } else { "blocked_unmapped_real_descriptor".to_string() }; @@ -1814,6 +1863,58 @@ fn determine_packed_event_import_outcome( "blocked_unsupported_decode".to_string() } +fn record_has_world_state_condition_rows(record: &SmpLoadedPackedEventRecordSummary) -> bool { + record + .decoded_conditions + .iter() + .any(runtime_condition_is_world_state) + || record + .standalone_condition_rows + .iter() + .any(ordinary_condition_row_is_world_state_family) +} + +fn runtime_condition_is_world_state(condition: &RuntimeCondition) -> bool { + matches!( + condition, + RuntimeCondition::SpecialConditionThreshold { .. } + | RuntimeCondition::CandidateAvailabilityThreshold { .. } + | RuntimeCondition::EconomicStatusCodeThreshold { .. } + ) +} + +fn ordinary_condition_row_is_world_state_family( + row: &SmpLoadedPackedEventConditionRowSummary, +) -> bool { + row.metric.as_deref().is_some_and(|metric| { + metric.contains("Special Condition") + || metric.contains("Candidate Availability") + || metric.contains("Economic Status") + || metric.contains("World Flag") + }) || row + .semantic_family + .as_deref() + .is_some_and(|family| family == "world_state_threshold" || family == "world_flag_equals") +} + +fn real_grouped_row_is_world_state_family( + row: &SmpLoadedPackedEventGroupedEffectRowSummary, +) -> bool { + row.target_mask_bits == Some(0x08) + || row.parameter_family.as_deref().is_some_and(|family| { + family.starts_with("whole_game_") + || family.starts_with("special_condition") + || family.starts_with("candidate_availability") + || family.starts_with("world_flag") + }) + || row.descriptor_label.as_deref().is_some_and(|label| { + label.contains("Special Condition") + || label.contains("Candidate Availability") + || label.contains("World Flag") + || label == "Economic Status" + }) +} + fn packed_record_company_target_import_blocker( record: &SmpLoadedPackedEventRecordSummary, company_context: &ImportRuntimeContext, @@ -1858,6 +1959,9 @@ fn runtime_condition_company_target_import_blocker( target, territory, .. } => company_target_import_blocker(target, company_context) .or_else(|| territory_target_import_blocker(territory, company_context)), + RuntimeCondition::SpecialConditionThreshold { .. } + | RuntimeCondition::CandidateAvailabilityThreshold { .. } + | RuntimeCondition::EconomicStatusCodeThreshold { .. } => None, } } @@ -1896,6 +2000,7 @@ fn company_target_import_outcome(blocker: ImportBlocker) -> &'static str { ImportBlocker::MissingTerritoryContext => "blocked_missing_territory_context", ImportBlocker::NamedTerritoryBinding => "blocked_named_territory_binding", ImportBlocker::UnmappedOrdinaryCondition => "blocked_unmapped_ordinary_condition", + ImportBlocker::UnmappedWorldCondition => "blocked_unmapped_world_condition", ImportBlocker::MissingTrainContext => "blocked_missing_train_context", ImportBlocker::MissingTrainTerritoryContext => "blocked_missing_train_territory_context", } @@ -4621,8 +4726,10 @@ mod tests { grouped_effect_row_counts: vec![1, 0, 0, 0], grouped_effect_rows: vec![real_territory_access_row( true, - vec!["territory access row is missing company or territory scope" - .to_string()], + vec![ + "territory access row is missing company or territory scope" + .to_string(), + ], )], decoded_conditions: Vec::new(), decoded_actions: vec![], diff --git a/crates/rrt-runtime/src/lib.rs b/crates/rrt-runtime/src/lib.rs index c86d463..77cc7c2 100644 --- a/crates/rrt-runtime/src/lib.rs +++ b/crates/rrt-runtime/src/lib.rs @@ -39,14 +39,13 @@ pub use runtime::{ RuntimeCompanyMetric, RuntimeCompanyTarget, RuntimeCompanyTerritoryAccess, RuntimeCompanyTerritoryTrackPieceCount, RuntimeCondition, RuntimeConditionComparator, RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate, - RuntimePackedEventCollectionSummary, - RuntimePackedEventCompactControlSummary, RuntimePackedEventConditionRowSummary, - RuntimePackedEventGroupedEffectRowSummary, RuntimePackedEventNegativeSentinelScopeSummary, - RuntimePackedEventRecordSummary, RuntimePackedEventTextBandSummary, RuntimePlayer, - RuntimePlayerConditionTestScope, RuntimePlayerTarget, RuntimeSaveProfileState, - RuntimeServiceState, RuntimeState, RuntimeTerritory, RuntimeTerritoryMetric, - RuntimeTerritoryTarget, RuntimeTrackMetric, RuntimeTrackPieceCounts, RuntimeTrain, - RuntimeWorldRestoreState, + RuntimePackedEventCollectionSummary, RuntimePackedEventCompactControlSummary, + RuntimePackedEventConditionRowSummary, RuntimePackedEventGroupedEffectRowSummary, + RuntimePackedEventNegativeSentinelScopeSummary, RuntimePackedEventRecordSummary, + RuntimePackedEventTextBandSummary, RuntimePlayer, RuntimePlayerConditionTestScope, + RuntimePlayerTarget, RuntimeSaveProfileState, RuntimeServiceState, RuntimeState, + RuntimeTerritory, RuntimeTerritoryMetric, RuntimeTerritoryTarget, RuntimeTrackMetric, + RuntimeTrackPieceCounts, RuntimeTrain, RuntimeWorldRestoreState, }; pub use smp::{ SMP_FOUR_SIDECAR_BYTE_PLANES_MIN_BUNDLE_VERSION, SmpAlignedRuntimeRuleBandLane, diff --git a/crates/rrt-runtime/src/runtime.rs b/crates/rrt-runtime/src/runtime.rs index 2fc5165..3c25c9f 100644 --- a/crates/rrt-runtime/src/runtime.rs +++ b/crates/rrt-runtime/src/runtime.rs @@ -228,6 +228,20 @@ pub enum RuntimeCondition { comparator: RuntimeConditionComparator, value: i64, }, + SpecialConditionThreshold { + label: String, + comparator: RuntimeConditionComparator, + value: i64, + }, + CandidateAvailabilityThreshold { + name: String, + comparator: RuntimeConditionComparator, + value: i64, + }, + EconomicStatusCodeThreshold { + comparator: RuntimeConditionComparator, + value: i64, + }, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -1235,6 +1249,21 @@ fn validate_runtime_condition( validate_company_target(target, valid_company_ids)?; validate_territory_target(territory, valid_territory_ids) } + RuntimeCondition::SpecialConditionThreshold { label, .. } => { + if label.trim().is_empty() { + Err("label must not be empty".to_string()) + } else { + Ok(()) + } + } + RuntimeCondition::CandidateAvailabilityThreshold { name, .. } => { + if name.trim().is_empty() { + Err("name must not be empty".to_string()) + } else { + Ok(()) + } + } + RuntimeCondition::EconomicStatusCodeThreshold { .. } => Ok(()), } } diff --git a/crates/rrt-runtime/src/smp.rs b/crates/rrt-runtime/src/smp.rs index f1e6cb0..8d453ec 100644 --- a/crates/rrt-runtime/src/smp.rs +++ b/crates/rrt-runtime/src/smp.rs @@ -2126,9 +2126,8 @@ fn parse_real_event_runtime_record_summary( && row.raw_scalar_value != 0 && (!company_target_present || !territory_target_present) { - row.notes.push( - "territory access row is missing company or territory scope".to_string(), - ); + row.notes + .push("territory access row is missing company or territory scope".to_string()); } } } @@ -2927,18 +2926,20 @@ fn runtime_effect_supported_for_save_import(effect: &RuntimeEffect) -> bool { ), RuntimeEffect::SetCompanyTerritoryAccess { target, territory, .. - } => matches!( - target, - RuntimeCompanyTarget::AllActive - | RuntimeCompanyTarget::Ids { .. } - | RuntimeCompanyTarget::HumanCompanies - | RuntimeCompanyTarget::AiCompanies - | RuntimeCompanyTarget::SelectedCompany - | RuntimeCompanyTarget::ConditionTrueCompany - ) && matches!( - territory, - RuntimeTerritoryTarget::AllTerritories | RuntimeTerritoryTarget::Ids { .. } - ), + } => { + matches!( + target, + RuntimeCompanyTarget::AllActive + | RuntimeCompanyTarget::Ids { .. } + | RuntimeCompanyTarget::HumanCompanies + | RuntimeCompanyTarget::AiCompanies + | RuntimeCompanyTarget::SelectedCompany + | RuntimeCompanyTarget::ConditionTrueCompany + ) && matches!( + territory, + RuntimeTerritoryTarget::AllTerritories | RuntimeTerritoryTarget::Ids { .. } + ) + } RuntimeEffect::SetCompanyCash { target, .. } | RuntimeEffect::AdjustCompanyCash { target, .. } | RuntimeEffect::AdjustCompanyDebt { target, .. } => matches!( @@ -2961,7 +2962,10 @@ fn runtime_condition_supported_for_save_import(condition: &RuntimeCondition) -> match condition { RuntimeCondition::CompanyNumericThreshold { .. } | RuntimeCondition::TerritoryNumericThreshold { .. } - | RuntimeCondition::CompanyTerritoryNumericThreshold { .. } => true, + | RuntimeCondition::CompanyTerritoryNumericThreshold { .. } + | RuntimeCondition::SpecialConditionThreshold { .. } + | RuntimeCondition::CandidateAvailabilityThreshold { .. } + | RuntimeCondition::EconomicStatusCodeThreshold { .. } => true, } } diff --git a/crates/rrt-runtime/src/step.rs b/crates/rrt-runtime/src/step.rs index 25b3a19..26baf7a 100644 --- a/crates/rrt-runtime/src/step.rs +++ b/crates/rrt-runtime/src/step.rs @@ -651,6 +651,46 @@ fn evaluate_record_conditions( return Ok(None); } } + RuntimeCondition::SpecialConditionThreshold { + label, + comparator, + value, + } => { + let actual = state + .special_conditions + .get(label) + .copied() + .map(i64::from) + .unwrap_or(0); + if !compare_condition_value(actual, *comparator, *value) { + return Ok(None); + } + } + RuntimeCondition::CandidateAvailabilityThreshold { + name, + comparator, + value, + } => { + let actual = state + .candidate_availability + .get(name) + .copied() + .map(i64::from) + .unwrap_or(0); + if !compare_condition_value(actual, *comparator, *value) { + return Ok(None); + } + } + RuntimeCondition::EconomicStatusCodeThreshold { comparator, value } => { + let actual = state + .world_restore + .economic_status_code + .map(i64::from) + .unwrap_or(0); + if !compare_condition_value(actual, *comparator, *value) { + return Ok(None); + } + } } } @@ -1039,7 +1079,8 @@ fn set_company_territory_access_pairs( } } else { access_entries.retain(|entry| { - !(company_ids.contains(&entry.company_id) && territory_ids.contains(&entry.territory_id)) + !(company_ids.contains(&entry.company_id) + && territory_ids.contains(&entry.territory_id)) }); } } @@ -1785,6 +1826,78 @@ mod tests { assert!(error.contains("condition-evaluation context")); } + #[test] + fn evaluates_world_state_conditions_before_effects_run() { + let mut state = RuntimeState { + world_restore: RuntimeWorldRestoreState { + economic_status_code: Some(3), + ..RuntimeWorldRestoreState::default() + }, + candidate_availability: BTreeMap::from([(String::from("Mogul"), 2)]), + special_conditions: BTreeMap::from([(String::from("Use Wartime Cargos"), 1)]), + event_runtime_records: vec![ + RuntimeEventRecord { + record_id: 23, + trigger_kind: 7, + active: true, + service_count: 0, + marks_collection_dirty: false, + one_shot: false, + has_fired: false, + conditions: vec![ + RuntimeCondition::SpecialConditionThreshold { + label: "Use Wartime Cargos".to_string(), + comparator: RuntimeConditionComparator::Ge, + value: 1, + }, + RuntimeCondition::CandidateAvailabilityThreshold { + name: "Mogul".to_string(), + comparator: RuntimeConditionComparator::Eq, + value: 2, + }, + RuntimeCondition::EconomicStatusCodeThreshold { + comparator: RuntimeConditionComparator::Eq, + value: 3, + }, + ], + effects: vec![RuntimeEffect::SetWorldFlag { + key: "world_condition_passed".to_string(), + value: true, + }], + }, + RuntimeEventRecord { + record_id: 24, + trigger_kind: 7, + active: true, + service_count: 0, + marks_collection_dirty: false, + one_shot: false, + has_fired: false, + conditions: vec![RuntimeCondition::SpecialConditionThreshold { + label: "Disable Cargo Economy".to_string(), + comparator: RuntimeConditionComparator::Gt, + value: 0, + }], + effects: vec![RuntimeEffect::SetWorldFlag { + key: "world_condition_failed".to_string(), + value: true, + }], + }, + ], + ..state() + }; + + let result = execute_step_command( + &mut state, + &StepCommand::ServiceTriggerKind { trigger_kind: 7 }, + ) + .expect("world-state conditions should evaluate successfully"); + + assert_eq!(result.service_events[0].serviced_record_ids, vec![23]); + assert_eq!(state.world_flags.get("world_condition_passed"), Some(&true)); + assert_eq!(state.world_flags.get("world_condition_failed"), None); + } + #[test] fn one_shot_record_only_fires_once() { let mut state = RuntimeState { diff --git a/crates/rrt-runtime/src/summary.rs b/crates/rrt-runtime/src/summary.rs index a0a0a84..eb56eaa 100644 --- a/crates/rrt-runtime/src/summary.rs +++ b/crates/rrt-runtime/src/summary.rs @@ -56,8 +56,10 @@ pub struct RuntimeSummary { 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_unmapped_world_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_unmapped_world_descriptor_count: usize, pub packed_event_blocked_territory_access_variant_count: usize, pub packed_event_blocked_territory_access_scope_count: usize, pub packed_event_blocked_missing_train_context_count: usize, @@ -393,6 +395,20 @@ impl RuntimeSummary { .count() }) .unwrap_or(0), + packed_event_blocked_unmapped_world_condition_count: state + .packed_event_collection + .as_ref() + .map(|summary| { + summary + .records + .iter() + .filter(|record| { + record.import_outcome.as_deref() + == Some("blocked_unmapped_world_condition") + }) + .count() + }) + .unwrap_or(0), packed_event_blocked_missing_compact_control_count: state .packed_event_collection .as_ref() @@ -421,6 +437,20 @@ impl RuntimeSummary { .count() }) .unwrap_or(0), + packed_event_blocked_unmapped_world_descriptor_count: state + .packed_event_collection + .as_ref() + .map(|summary| { + summary + .records + .iter() + .filter(|record| { + record.import_outcome.as_deref() + == Some("blocked_unmapped_world_descriptor") + }) + .count() + }) + .unwrap_or(0), packed_event_blocked_territory_access_variant_count: state .packed_event_collection .as_ref() @@ -843,4 +873,107 @@ mod tests { assert_eq!(summary.company_count, 2); assert_eq!(summary.active_company_count, 1); } + + #[test] + fn counts_world_frontier_buckets_separately() { + let state = RuntimeState { + calendar: CalendarPoint { + year: 1830, + month_slot: 0, + phase_slot: 0, + tick_slot: 0, + }, + world_flags: BTreeMap::new(), + save_profile: RuntimeSaveProfileState::default(), + world_restore: RuntimeWorldRestoreState::default(), + metadata: BTreeMap::new(), + companies: Vec::new(), + selected_company_id: None, + players: Vec::new(), + selected_player_id: None, + trains: Vec::new(), + territories: Vec::new(), + company_territory_track_piece_counts: Vec::new(), + company_territory_access: Vec::new(), + packed_event_collection: Some(RuntimePackedEventCollectionSummary { + source_kind: "packed-event-runtime-collection".to_string(), + mechanism_family: "classic-save-rehydrate-v1".to_string(), + mechanism_confidence: "grounded".to_string(), + container_profile_family: Some("rt3-classic-save-container-v1".to_string()), + packed_state_version: 0x3e9, + packed_state_version_hex: "0x000003e9".to_string(), + live_id_bound: 2, + live_record_count: 2, + live_entry_ids: vec![21, 22], + decoded_record_count: 2, + imported_runtime_record_count: 0, + records: vec![ + RuntimePackedEventRecordSummary { + record_index: 0, + live_entry_id: 21, + payload_offset: Some(0x7202), + payload_len: Some(96), + decode_status: "parity_only".to_string(), + payload_family: "real_packed_v1".to_string(), + trigger_kind: Some(7), + active: None, + marks_collection_dirty: None, + one_shot: None, + compact_control: None, + text_bands: Vec::new(), + standalone_condition_row_count: 0, + standalone_condition_rows: Vec::new(), + negative_sentinel_scope: None, + grouped_effect_row_counts: vec![1, 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_unmapped_world_descriptor".to_string()), + notes: Vec::new(), + }, + RuntimePackedEventRecordSummary { + record_index: 1, + live_entry_id: 22, + payload_offset: Some(0x7242), + payload_len: Some(96), + decode_status: "parity_only".to_string(), + payload_family: "real_packed_v1".to_string(), + trigger_kind: Some(7), + active: None, + marks_collection_dirty: None, + one_shot: None, + compact_control: None, + text_bands: Vec::new(), + standalone_condition_row_count: 1, + standalone_condition_rows: Vec::new(), + negative_sentinel_scope: None, + 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_unmapped_world_condition".to_string()), + notes: Vec::new(), + }, + ], + }), + event_runtime_records: Vec::new(), + candidate_availability: BTreeMap::new(), + special_conditions: BTreeMap::new(), + service_state: RuntimeServiceState::default(), + }; + + let summary = RuntimeSummary::from_state(&state); + assert_eq!( + summary.packed_event_blocked_unmapped_world_descriptor_count, + 1 + ); + assert_eq!( + summary.packed_event_blocked_unmapped_world_condition_count, + 1 + ); + } } diff --git a/docs/README.md b/docs/README.md index 2121126..6c527b2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -97,6 +97,10 @@ The highest-value next passes are now: - descriptor `3` `Territory - Allow All` now executes as company-to-territory access rights through the same ordinary runtime path; shell purchase-flow parity remains out of scope, and mixed supported/unsupported real rows still stay parity-only +- whole-game ordinary-condition execution now exists too: special-condition thresholds, + candidate-availability thresholds, and economic-status-code thresholds now gate imported runtime + records, and the packed-event frontier now reports explicit unmapped world-condition and + world-descriptor buckets - 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 diff --git a/docs/runtime-rehost-plan.md b/docs/runtime-rehost-plan.md index 8d480c2..cc2c914 100644 --- a/docs/runtime-rehost-plan.md +++ b/docs/runtime-rehost-plan.md @@ -47,11 +47,16 @@ Implemented today: - descriptor `3` = `Territory - Allow All` now imports and executes too, reinterpreted as company-to-territory access rights instead of a territory-owned policy bit; shell purchase-flow and selected-profile parity still remain outside the runtime surface +- the first whole-game ordinary-condition batch now executes too: special-condition thresholds, + candidate-availability thresholds, and economic-status-code thresholds now gate runtime records + through the same service path, and whole-game parity frontiers now report explicit unmapped + world-condition and world-descriptor buckets rather than falling back to the generic ordinary + or descriptor counts That means the next implementation work is breadth, not bootstrap. The recommended next slice is broader real grouped-descriptor and ordinary condition-id coverage beyond the current access, -world, train, player, and numeric-threshold batches, plus richer runtime ownership only where a -later descriptor family needs more than the current event-owned roster. +whole-game, train, player, and numeric-threshold batches, plus richer runtime ownership only where +a later descriptor or condition family needs more than the current event-owned roster. ## Why This Boundary @@ -396,8 +401,8 @@ Checked-in fixture families already include: ## Next Slice The recommended next implementation slice is broader ordinary-condition and grouped-descriptor -breadth on top of the now-stable numeric-threshold, overlay-context, named-territory, player, -world/train, and company-territory-access batches. +breadth on top of the now-stable numeric-threshold, whole-game-state, overlay-context, +named-territory, player, world/train, and company-territory-access batches. Target behavior: @@ -405,9 +410,9 @@ Target behavior: 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 +- extend ordinary condition coverage beyond the current numeric-threshold and whole-game-state + families 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 on exact case-sensitive binding until captured evidence justifies alias tables or fuzzier matching - keep player-owned condition scope within the minimal event runtime model until later slices need @@ -430,6 +435,9 @@ Fixture work for that slice: - preserve the new ordinary-condition tracked overlays for executable company finance, company track, aggregate territory track, and company-territory track thresholds +- preserve the new whole-game tracked save-slice fixtures for executable special-condition and + candidate-availability/economic-status batches, plus the parity-only world-condition and + world-descriptor frontier sample - preserve the named-territory no-match tracked overlay as the explicit binding blocker frontier - preserve the territory-access tracked overlays and parity samples so descriptor `3` access-rights execution does not regress while other grouped families widen diff --git a/fixtures/runtime/packed-event-world-condition-gated-save-slice-fixture.json b/fixtures/runtime/packed-event-world-condition-gated-save-slice-fixture.json new file mode 100644 index 0000000..f01236a --- /dev/null +++ b/fixtures/runtime/packed-event-world-condition-gated-save-slice-fixture.json @@ -0,0 +1,81 @@ +{ + "format_version": 1, + "fixture_id": "packed-event-world-condition-gated-save-slice-fixture", + "source": { + "kind": "captured-runtime", + "description": "Fixture proving whole-game ordinary conditions gate imported world-state effects." + }, + "state_save_slice_path": "packed-event-world-condition-gated-save-slice.json", + "commands": [ + { + "kind": "service_trigger_kind", + "trigger_kind": 6 + }, + { + "kind": "service_trigger_kind", + "trigger_kind": 7 + } + ], + "expected_summary": { + "packed_event_collection_present": true, + "packed_event_record_count": 2, + "packed_event_decoded_record_count": 2, + "packed_event_imported_runtime_record_count": 2, + "event_runtime_record_count": 2, + "candidate_availability_count": 2, + "zero_candidate_availability_count": 0, + "special_condition_count": 1, + "enabled_special_condition_count": 1, + "world_restore_economic_status_code": 4, + "total_event_record_service_count": 2, + "total_trigger_dispatch_count": 2 + }, + "expected_state_fragment": { + "world_restore": { + "economic_status_code": 4 + }, + "candidate_availability": { + "Mogul": 2, + "Turbo Diesel": 1 + }, + "packed_event_collection": { + "records": [ + { + "import_outcome": "imported" + }, + { + "import_outcome": "imported", + "decoded_conditions": [ + { + "kind": "special_condition_threshold", + "label": "Use Wartime Cargos", + "comparator": "ge", + "value": 1 + }, + { + "kind": "candidate_availability_threshold", + "name": "Mogul", + "comparator": "ge", + "value": 2 + }, + { + "kind": "economic_status_code_threshold", + "comparator": "ge", + "value": 4 + } + ] + } + ] + }, + "event_runtime_records": [ + { + "record_id": 41, + "service_count": 1 + }, + { + "record_id": 44, + "service_count": 1 + } + ] + } +} diff --git a/fixtures/runtime/packed-event-world-condition-gated-save-slice.json b/fixtures/runtime/packed-event-world-condition-gated-save-slice.json new file mode 100644 index 0000000..2316f5c --- /dev/null +++ b/fixtures/runtime/packed-event-world-condition-gated-save-slice.json @@ -0,0 +1,297 @@ +{ + "format_version": 1, + "save_slice_id": "packed-event-world-condition-gated-save-slice", + "source": { + "description": "Tracked save-slice document with whole-game conditions gating a whole-game effect.", + "original_save_filename": "captured-world-condition-gated.gms", + "original_save_sha256": "world-condition-gated-sample-sha256", + "notes": [ + "tracked as JSON save-slice document rather than raw .smp", + "proves whole-game ordinary conditions gate imported runtime effects" + ] + }, + "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": { + "source_kind": "save-bridge-secondary-block", + "semantic_family": "scenario-named-candidate-availability-table", + "header_offset": 27248, + "entries_offset": 27345, + "entries_end_offset": 27432, + "observed_entry_count": 2, + "zero_availability_count": 1, + "zero_availability_names": [ + "Turbo Diesel" + ], + "footer_progress_hex_words": [ + "0x000032dc", + "0x00003714" + ], + "entries": [ + { + "index": 0, + "offset": 27345, + "text": "Mogul", + "availability_dword": 2, + "availability_dword_hex": "0x00000002", + "trailer_word": 2, + "trailer_word_hex": "0x00000002" + }, + { + "index": 1, + "offset": 27373, + "text": "Turbo Diesel", + "availability_dword": 0, + "availability_dword_hex": "0x00000000", + "trailer_word": 0, + "trailer_word_hex": "0x00000000" + } + ] + }, + "special_conditions_table": { + "source_kind": "save-fixed-special-conditions-range", + "table_offset": 3428, + "table_len": 144, + "enabled_visible_count": 1, + "enabled_visible_labels": [ + "Use Wartime Cargos" + ], + "entries": [ + { + "slot_index": 0, + "hidden": false, + "label_id": 2874, + "help_id": 2875, + "label": "Use Wartime Cargos", + "value": 1, + "value_hex": "0x00000001" + }, + { + "slot_index": 35, + "hidden": true, + "label_id": 3, + "help_id": 3, + "label": "Hidden sentinel", + "value": 1, + "value_hex": "0x00000001" + } + ] + }, + "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": 30528, + "records_tag_offset": 30784, + "close_tag_offset": 31616, + "packed_state_version": 1001, + "packed_state_version_hex": "0x000003e9", + "live_id_bound": 44, + "live_record_count": 2, + "live_entry_ids": [41, 44], + "decoded_record_count": 2, + "imported_runtime_record_count": 2, + "records": [ + { + "record_index": 0, + "live_entry_id": 41, + "payload_offset": 30848, + "payload_len": 96, + "decode_status": "parity_only", + "payload_family": "real_packed_v1", + "trigger_kind": 6, + "one_shot": false, + "compact_control": { + "mode_byte_0x7ef": 6, + "primary_selector_0x7f0": 0, + "grouped_mode_0x7f4": 2, + "one_shot_header_0x7f5": 0, + "modifier_flag_0x7f9": 0, + "modifier_flag_0x7fa": 0, + "grouped_target_scope_ordinals_0x7fb": [0, 0, 0, 0], + "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": 0, + "standalone_condition_rows": [], + "negative_sentinel_scope": null, + "grouped_effect_row_counts": [1, 0, 0, 0], + "grouped_effect_rows": [ + { + "group_index": 0, + "row_index": 0, + "descriptor_id": 8, + "descriptor_label": "Economic Status", + "target_mask_bits": 8, + "parameter_family": "whole_game_state_enum", + "opcode": 3, + "raw_scalar_value": 4, + "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 Economic Status to 4", + "locomotive_name": null, + "notes": [] + } + ], + "decoded_conditions": [], + "decoded_actions": [ + { + "kind": "set_economic_status_code", + "value": 4 + } + ], + "executable_import_ready": true, + "notes": [ + "world-side setup record" + ] + }, + { + "record_index": 1, + "live_entry_id": 44, + "payload_offset": 30976, + "payload_len": 184, + "decode_status": "parity_only", + "payload_family": "real_packed_v1", + "trigger_kind": 7, + "one_shot": false, + "compact_control": { + "mode_byte_0x7ef": 7, + "primary_selector_0x7f0": 0, + "grouped_mode_0x7f4": 2, + "one_shot_header_0x7f5": 0, + "modifier_flag_0x7f9": 0, + "modifier_flag_0x7fa": 0, + "grouped_target_scope_ordinals_0x7fb": [0, 0, 0, 0], + "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": 3, + "standalone_condition_rows": [ + { + "row_index": 0, + "raw_condition_id": 3901, + "subtype": 0, + "flag_bytes": [1, 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": "Special Condition: Use Wartime Cargos", + "semantic_family": "world_state_threshold", + "semantic_preview": "Test Use Wartime Cargos >= 1", + "requires_candidate_name_binding": false, + "notes": [ + "tracked whole-game condition sample" + ] + }, + { + "row_index": 1, + "raw_condition_id": 3902, + "subtype": 0, + "flag_bytes": [2, 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": "Candidate Availability: Mogul", + "semantic_family": "world_state_threshold", + "semantic_preview": "Test Mogul >= 2", + "requires_candidate_name_binding": false, + "notes": [ + "tracked whole-game condition sample" + ] + }, + { + "row_index": 2, + "raw_condition_id": 3903, + "subtype": 0, + "flag_bytes": [4, 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": "Economic Status", + "semantic_family": "world_state_threshold", + "semantic_preview": "Test Economic Status >= 4", + "requires_candidate_name_binding": false, + "notes": [ + "tracked whole-game condition sample" + ] + } + ], + "negative_sentinel_scope": null, + "grouped_effect_row_counts": [1, 0, 0, 0], + "grouped_effect_rows": [ + { + "group_index": 0, + "row_index": 0, + "descriptor_id": 109, + "descriptor_label": "Turbo Diesel Availability", + "target_mask_bits": 8, + "parameter_family": "candidate_availability_scalar", + "opcode": 3, + "raw_scalar_value": 1, + "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 Turbo Diesel Availability to 1", + "locomotive_name": null, + "notes": [ + "tracked whole-game grouped-effect sample" + ] + } + ], + "decoded_conditions": [ + { + "kind": "special_condition_threshold", + "label": "Use Wartime Cargos", + "comparator": "ge", + "value": 1 + }, + { + "kind": "candidate_availability_threshold", + "name": "Mogul", + "comparator": "ge", + "value": 2 + }, + { + "kind": "economic_status_code_threshold", + "comparator": "ge", + "value": 4 + } + ], + "decoded_actions": [ + { + "kind": "set_candidate_availability", + "name": "Turbo Diesel", + "value": 1 + } + ], + "executable_import_ready": true, + "notes": [ + "whole-game ordinary conditions gate a whole-game effect" + ] + } + ] + }, + "notes": [ + "whole-game condition-gated effect sample" + ] + } +} diff --git a/fixtures/runtime/packed-event-world-parity-save-slice-fixture.json b/fixtures/runtime/packed-event-world-parity-save-slice-fixture.json new file mode 100644 index 0000000..646217c --- /dev/null +++ b/fixtures/runtime/packed-event-world-parity-save-slice-fixture.json @@ -0,0 +1,36 @@ +{ + "format_version": 1, + "fixture_id": "packed-event-world-parity-save-slice-fixture", + "source": { + "kind": "captured-runtime", + "description": "Fixture keeping the current whole-game descriptor and condition frontier explicit." + }, + "state_save_slice_path": "packed-event-world-parity-save-slice.json", + "commands": [ + { + "kind": "service_trigger_kind", + "trigger_kind": 7 + } + ], + "expected_summary": { + "packed_event_collection_present": true, + "packed_event_record_count": 2, + "packed_event_decoded_record_count": 2, + "packed_event_imported_runtime_record_count": 0, + "event_runtime_record_count": 0, + "packed_event_blocked_unmapped_world_descriptor_count": 1, + "packed_event_blocked_unmapped_world_condition_count": 1 + }, + "expected_state_fragment": { + "packed_event_collection": { + "records": [ + { + "import_outcome": "blocked_unmapped_world_descriptor" + }, + { + "import_outcome": "blocked_unmapped_world_condition" + } + ] + } + } +} diff --git a/fixtures/runtime/packed-event-world-parity-save-slice.json b/fixtures/runtime/packed-event-world-parity-save-slice.json new file mode 100644 index 0000000..bf1efa1 --- /dev/null +++ b/fixtures/runtime/packed-event-world-parity-save-slice.json @@ -0,0 +1,153 @@ +{ + "format_version": 1, + "save_slice_id": "packed-event-world-parity-save-slice", + "source": { + "description": "Tracked save-slice document preserving the current whole-game descriptor and condition frontier.", + "original_save_filename": "captured-world-parity.gms", + "original_save_sha256": "world-parity-sample-sha256", + "notes": [ + "tracked as JSON save-slice document rather than raw .smp", + "keeps one unmapped world descriptor and one unmapped world condition explicit" + ] + }, + "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": 32000, + "records_tag_offset": 32256, + "close_tag_offset": 33024, + "packed_state_version": 1001, + "packed_state_version_hex": "0x000003e9", + "live_id_bound": 52, + "live_record_count": 2, + "live_entry_ids": [49, 52], + "decoded_record_count": 2, + "imported_runtime_record_count": 0, + "records": [ + { + "record_index": 0, + "live_entry_id": 49, + "payload_offset": 32320, + "payload_len": 112, + "decode_status": "parity_only", + "payload_family": "real_packed_v1", + "trigger_kind": 6, + "one_shot": false, + "compact_control": { + "mode_byte_0x7ef": 6, + "primary_selector_0x7f0": 0, + "grouped_mode_0x7f4": 2, + "one_shot_header_0x7f5": 0, + "modifier_flag_0x7f9": 0, + "modifier_flag_0x7fa": 0, + "grouped_target_scope_ordinals_0x7fb": [0, 0, 0, 0], + "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": 0, + "standalone_condition_rows": [], + "negative_sentinel_scope": null, + "grouped_effect_row_counts": [1, 0, 0, 0], + "grouped_effect_rows": [ + { + "group_index": 0, + "row_index": 0, + "descriptor_id": 110, + "descriptor_label": "Disable Stock Buying and Selling", + "target_mask_bits": 8, + "parameter_family": "world_flag_toggle", + "opcode": 0, + "raw_scalar_value": 1, + "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": "bool_toggle", + "semantic_family": "bool_toggle", + "semantic_preview": "Set Disable Stock Buying and Selling to TRUE", + "locomotive_name": null, + "notes": [ + "recovered whole-game descriptor family without a checked-in executable mapping yet" + ] + } + ], + "decoded_conditions": [], + "decoded_actions": [], + "executable_import_ready": false, + "notes": [ + "world-side descriptor remains parity-only" + ] + }, + { + "record_index": 1, + "live_entry_id": 52, + "payload_offset": 32464, + "payload_len": 120, + "decode_status": "parity_only", + "payload_family": "real_packed_v1", + "trigger_kind": 7, + "one_shot": false, + "compact_control": { + "mode_byte_0x7ef": 7, + "primary_selector_0x7f0": 0, + "grouped_mode_0x7f4": 2, + "one_shot_header_0x7f5": 0, + "modifier_flag_0x7f9": 0, + "modifier_flag_0x7fa": 0, + "grouped_target_scope_ordinals_0x7fb": [0, 0, 0, 0], + "grouped_scope_checkboxes_0x7ff": [0, 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": 3904, + "subtype": 0, + "flag_bytes": [1, 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": "Special Condition: Disable Stock Buying and Selling", + "semantic_family": "world_state_threshold", + "semantic_preview": "Test Disable Stock Buying and Selling >= 1", + "requires_candidate_name_binding": false, + "notes": [ + "recovered world-side ordinary condition family without a checked-in executable mapping yet" + ] + } + ], + "negative_sentinel_scope": null, + "grouped_effect_row_counts": [0, 0, 0, 0], + "grouped_effect_rows": [], + "decoded_conditions": [], + "decoded_actions": [], + "executable_import_ready": false, + "notes": [ + "world-side ordinary condition remains parity-only" + ] + } + ] + }, + "notes": [ + "whole-game parity frontier sample" + ] + } +} diff --git a/fixtures/runtime/packed-event-world-special-condition-save-slice-fixture.json b/fixtures/runtime/packed-event-world-special-condition-save-slice-fixture.json new file mode 100644 index 0000000..c19215d --- /dev/null +++ b/fixtures/runtime/packed-event-world-special-condition-save-slice-fixture.json @@ -0,0 +1,45 @@ +{ + "format_version": 1, + "fixture_id": "packed-event-world-special-condition-save-slice-fixture", + "source": { + "kind": "captured-runtime", + "description": "Fixture proving a whole-game special-condition effect imports and executes through the ordinary runtime path." + }, + "state_save_slice_path": "packed-event-world-special-condition-save-slice.json", + "commands": [ + { + "kind": "service_trigger_kind", + "trigger_kind": 7 + } + ], + "expected_summary": { + "packed_event_collection_present": true, + "packed_event_record_count": 1, + "packed_event_decoded_record_count": 1, + "packed_event_imported_runtime_record_count": 1, + "event_runtime_record_count": 1, + "special_condition_count": 1, + "enabled_special_condition_count": 1, + "total_event_record_service_count": 1, + "total_trigger_dispatch_count": 1 + }, + "expected_state_fragment": { + "special_conditions": { + "Use Wartime Cargos": 1 + }, + "packed_event_collection": { + "records": [ + { + "import_outcome": "imported", + "decoded_actions": [ + { + "kind": "set_special_condition", + "label": "Use Wartime Cargos", + "value": 1 + } + ] + } + ] + } + } +} diff --git a/fixtures/runtime/packed-event-world-special-condition-save-slice.json b/fixtures/runtime/packed-event-world-special-condition-save-slice.json new file mode 100644 index 0000000..a9af772 --- /dev/null +++ b/fixtures/runtime/packed-event-world-special-condition-save-slice.json @@ -0,0 +1,109 @@ +{ + "format_version": 1, + "save_slice_id": "packed-event-world-special-condition-save-slice", + "source": { + "description": "Tracked save-slice document with a whole-game special-condition effect row.", + "original_save_filename": "captured-world-special-condition.gms", + "original_save_sha256": "world-special-condition-sample-sha256", + "notes": [ + "tracked as JSON save-slice document rather than raw .smp", + "proves whole-game special-condition effects import through the ordinary runtime 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": 30000, + "records_tag_offset": 30256, + "close_tag_offset": 30720, + "packed_state_version": 1001, + "packed_state_version_hex": "0x000003e9", + "live_id_bound": 12, + "live_record_count": 1, + "live_entry_ids": [12], + "decoded_record_count": 1, + "imported_runtime_record_count": 1, + "records": [ + { + "record_index": 0, + "live_entry_id": 12, + "payload_offset": 30336, + "payload_len": 112, + "decode_status": "parity_only", + "payload_family": "real_packed_v1", + "trigger_kind": 7, + "one_shot": false, + "compact_control": { + "mode_byte_0x7ef": 7, + "primary_selector_0x7f0": 0, + "grouped_mode_0x7f4": 2, + "one_shot_header_0x7f5": 0, + "modifier_flag_0x7f9": 0, + "modifier_flag_0x7fa": 0, + "grouped_target_scope_ordinals_0x7fb": [0, 0, 0, 0], + "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": 0, + "standalone_condition_rows": [], + "negative_sentinel_scope": null, + "grouped_effect_row_counts": [1, 0, 0, 0], + "grouped_effect_rows": [ + { + "group_index": 0, + "row_index": 0, + "descriptor_id": 108, + "descriptor_label": "Use Wartime Cargos", + "target_mask_bits": 8, + "parameter_family": "special_condition_scalar", + "opcode": 3, + "raw_scalar_value": 1, + "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 Use Wartime Cargos to 1", + "locomotive_name": null, + "notes": [ + "tracked world-side descriptor sample" + ] + } + ], + "decoded_conditions": [], + "decoded_actions": [ + { + "kind": "set_special_condition", + "label": "Use Wartime Cargos", + "value": 1 + } + ], + "executable_import_ready": true, + "notes": [ + "tracked whole-game grouped-effect import sample" + ] + } + ] + }, + "notes": [ + "whole-game special-condition effect sample" + ] + } +}