From f623c6dcc90ce1db324eedf317e403a3fe48bf01 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Thu, 16 Apr 2026 21:42:20 -0700 Subject: [PATCH] Execute aggregate cargo economics descriptors --- README.md | 7 +- .../event-effects-semantic-catalog.json | 60 +- crates/rrt-cli/src/main.rs | 9 + crates/rrt-fixtures/src/load.rs | 12 + crates/rrt-runtime/src/import.rs | 655 +++++++++++++++++- crates/rrt-runtime/src/lib.rs | 3 +- crates/rrt-runtime/src/persistence.rs | 6 + crates/rrt-runtime/src/runtime.rs | 162 ++++- crates/rrt-runtime/src/smp.rs | 129 +++- crates/rrt-runtime/src/step.rs | 69 +- crates/rrt-runtime/src/summary.rs | 48 ++ docs/README.md | 5 + docs/runtime-rehost-plan.md | 5 + ...o-economics-parity-save-slice-fixture.json | 50 ++ ...ent-cargo-economics-parity-save-slice.json | 137 ++++ ...nt-cargo-economics-save-slice-fixture.json | 75 ++ ...cked-event-cargo-economics-save-slice.json | 216 ++++++ .../py/build_event_effect_semantic_catalog.py | 16 +- 18 files changed, 1600 insertions(+), 64 deletions(-) create mode 100644 fixtures/runtime/packed-event-cargo-economics-parity-save-slice-fixture.json create mode 100644 fixtures/runtime/packed-event-cargo-economics-parity-save-slice.json create mode 100644 fixtures/runtime/packed-event-cargo-economics-save-slice-fixture.json create mode 100644 fixtures/runtime/packed-event-cargo-economics-save-slice.json diff --git a/README.md b/README.md index 09bfb0f..105b714 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,12 @@ descriptor residue. The recovered whole-game scalar economy/performance strip `5 bounded runtime landing surface too: representative descriptors import into `RuntimeState.world_scalar_overrides` through stable normalized keys such as `world.build_stations_cost`, `world.track_maintenance_cost`, `world.all_engine_speeds`, and -`world.hotel_revenue`. The first grounded +`world.hotel_revenue`. The grounded aggregate cargo-economics descriptors now have bounded +runtime landing surfaces too: descriptor `105` `All Cargo Prices` plus descriptors `177..179` +`All Cargo Production` / `All Factory Production` / `All Farm/Mine Production` import into +event-owned cargo override state, while the named cargo-price and named cargo-production strips +remain explicit `blocked_evidence_blocked_descriptor` parity until descriptor ordering is pinned +more strongly. The first grounded condition-side unlock now exists for 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 diff --git a/artifacts/exports/rt3-1.06/event-effects-semantic-catalog.json b/artifacts/exports/rt3-1.06/event-effects-semantic-catalog.json index a2505c9..1baea2c 100644 --- a/artifacts/exports/rt3-1.06/event-effects-semantic-catalog.json +++ b/artifacts/exports/rt3-1.06/event-effects-semantic-catalog.json @@ -954,8 +954,8 @@ "target_mask_bits": 8, "parameter_family": "cargo_price_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 106, @@ -1602,8 +1602,8 @@ "target_mask_bits": 15, "parameter_family": "cargo_production_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 178, @@ -1611,8 +1611,8 @@ "target_mask_bits": 15, "parameter_family": "cargo_production_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 179, @@ -1620,8 +1620,8 @@ "target_mask_bits": 15, "parameter_family": "cargo_production_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 180, @@ -2079,8 +2079,8 @@ "target_mask_bits": 15, "parameter_family": "cargo_production_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 231, @@ -2088,8 +2088,8 @@ "target_mask_bits": 15, "parameter_family": "cargo_production_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 232, @@ -2097,8 +2097,8 @@ "target_mask_bits": 15, "parameter_family": "cargo_production_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 233, @@ -2106,8 +2106,8 @@ "target_mask_bits": 15, "parameter_family": "cargo_production_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 234, @@ -2115,8 +2115,8 @@ "target_mask_bits": 15, "parameter_family": "cargo_production_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 235, @@ -2124,8 +2124,8 @@ "target_mask_bits": 15, "parameter_family": "cargo_production_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 236, @@ -2133,8 +2133,8 @@ "target_mask_bits": 15, "parameter_family": "cargo_production_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 237, @@ -2142,8 +2142,8 @@ "target_mask_bits": 15, "parameter_family": "cargo_production_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 238, @@ -2151,8 +2151,8 @@ "target_mask_bits": 15, "parameter_family": "cargo_production_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 239, @@ -2160,8 +2160,8 @@ "target_mask_bits": 15, "parameter_family": "cargo_production_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 240, @@ -2169,8 +2169,8 @@ "target_mask_bits": 15, "parameter_family": "cargo_production_scalar", "runtime_key": null, - "runtime_status": "evidence_blocked", - "executable_in_runtime": false + "runtime_status": "executable", + "executable_in_runtime": true }, { "descriptor_id": 241, diff --git a/crates/rrt-cli/src/main.rs b/crates/rrt-cli/src/main.rs index f907dd5..8e7e951 100644 --- a/crates/rrt-cli/src/main.rs +++ b/crates/rrt-cli/src/main.rs @@ -4479,6 +4479,11 @@ mod tests { let world_scalar_override_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join( "../../fixtures/runtime/packed-event-world-scalar-override-save-slice-fixture.json", ); + let cargo_economics_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("../../fixtures/runtime/packed-event-cargo-economics-save-slice-fixture.json"); + let cargo_economics_parity_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join( + "../../fixtures/runtime/packed-event-cargo-economics-parity-save-slice-fixture.json", + ); let world_scalar_condition_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join( "../../fixtures/runtime/packed-event-world-scalar-condition-save-slice-fixture.json", ); @@ -4590,6 +4595,10 @@ mod tests { .expect("save-slice-backed executable world-scalar fixture should summarize"); run_runtime_summarize_fixture(&world_scalar_override_fixture) .expect("save-slice-backed world-scalar override fixture should summarize"); + run_runtime_summarize_fixture(&cargo_economics_fixture) + .expect("save-slice-backed cargo-economics fixture should summarize"); + run_runtime_summarize_fixture(&cargo_economics_parity_fixture) + .expect("save-slice-backed cargo-economics parity fixture should summarize"); run_runtime_summarize_fixture(&world_scalar_condition_fixture) .expect("save-slice-backed executable world-scalar condition fixture should summarize"); run_runtime_summarize_fixture(&world_scalar_condition_parity_fixture) diff --git a/crates/rrt-fixtures/src/load.rs b/crates/rrt-fixtures/src/load.rs index 12c2467..9f0a9e5 100644 --- a/crates/rrt-fixtures/src/load.rs +++ b/crates/rrt-fixtures/src/load.rs @@ -189,6 +189,12 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), @@ -377,6 +383,12 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), diff --git a/crates/rrt-runtime/src/import.rs b/crates/rrt-runtime/src/import.rs index ff92b6c..af98952 100644 --- a/crates/rrt-runtime/src/import.rs +++ b/crates/rrt-runtime/src/import.rs @@ -5,10 +5,11 @@ use serde::{Deserialize, Serialize}; use crate::persistence::{load_runtime_snapshot_document, validate_runtime_snapshot_document}; use crate::{ - CalendarPoint, RuntimeCargoCatalogEntry, RuntimeChairmanProfile, RuntimeChairmanTarget, - RuntimeCompany, RuntimeCompanyConditionTestScope, RuntimeCompanyControllerKind, - RuntimeCompanyTarget, RuntimeCondition, RuntimeEffect, RuntimeEventRecord, - RuntimeEventRecordTemplate, RuntimeLocomotiveCatalogEntry, RuntimePackedEventCollectionSummary, + CalendarPoint, RuntimeCargoCatalogEntry, RuntimeCargoPriceTarget, RuntimeCargoProductionTarget, + RuntimeChairmanProfile, RuntimeChairmanTarget, RuntimeCompany, + RuntimeCompanyConditionTestScope, RuntimeCompanyControllerKind, RuntimeCompanyTarget, + RuntimeCondition, RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate, + RuntimeLocomotiveCatalogEntry, RuntimePackedEventCollectionSummary, RuntimePackedEventCompactControlSummary, RuntimePackedEventConditionRowSummary, RuntimePackedEventGroupedEffectRowSummary, RuntimePackedEventNegativeSentinelScopeSummary, RuntimePackedEventRecordSummary, RuntimePackedEventTextBandSummary, @@ -105,6 +106,12 @@ struct SaveSliceProjection { locomotive_catalog: Option>, cargo_catalog: Option>, named_locomotive_cost: BTreeMap, + all_cargo_price_override: Option, + named_cargo_price_overrides: BTreeMap, + all_cargo_production_override: Option, + factory_cargo_production_override: Option, + farm_mine_cargo_production_override: Option, + named_cargo_production_overrides: BTreeMap, cargo_production_overrides: BTreeMap, world_scalar_overrides: BTreeMap, special_conditions: BTreeMap, @@ -277,6 +284,12 @@ pub fn project_save_slice_to_runtime_state_import( candidate_availability: projection.candidate_availability, named_locomotive_availability: projection.named_locomotive_availability, named_locomotive_cost: projection.named_locomotive_cost, + all_cargo_price_override: projection.all_cargo_price_override, + named_cargo_price_overrides: projection.named_cargo_price_overrides, + all_cargo_production_override: projection.all_cargo_production_override, + factory_cargo_production_override: projection.factory_cargo_production_override, + farm_mine_cargo_production_override: projection.farm_mine_cargo_production_override, + named_cargo_production_overrides: projection.named_cargo_production_overrides, cargo_production_overrides: projection.cargo_production_overrides, world_scalar_overrides: projection.world_scalar_overrides, special_conditions: projection.special_conditions, @@ -365,6 +378,12 @@ pub fn project_save_slice_overlay_to_runtime_state_import( candidate_availability: projection.candidate_availability, named_locomotive_availability: projection.named_locomotive_availability, named_locomotive_cost: base_state.named_locomotive_cost.clone(), + all_cargo_price_override: base_state.all_cargo_price_override, + named_cargo_price_overrides: base_state.named_cargo_price_overrides.clone(), + all_cargo_production_override: base_state.all_cargo_production_override, + factory_cargo_production_override: base_state.factory_cargo_production_override, + farm_mine_cargo_production_override: base_state.farm_mine_cargo_production_override, + named_cargo_production_overrides: base_state.named_cargo_production_overrides.clone(), cargo_production_overrides: base_state.cargo_production_overrides.clone(), world_scalar_overrides: base_state.world_scalar_overrides.clone(), special_conditions: projection.special_conditions, @@ -874,6 +893,12 @@ fn project_save_slice_components( }; let named_locomotive_cost = BTreeMap::new(); + let all_cargo_price_override = None; + let named_cargo_price_overrides = BTreeMap::new(); + let all_cargo_production_override = None; + let factory_cargo_production_override = None; + let farm_mine_cargo_production_override = None; + let named_cargo_production_overrides = BTreeMap::new(); let cargo_production_overrides = BTreeMap::new(); let world_scalar_overrides = BTreeMap::new(); @@ -951,6 +976,12 @@ fn project_save_slice_components( locomotive_catalog, cargo_catalog, named_locomotive_cost, + all_cargo_price_override, + named_cargo_price_overrides, + all_cargo_production_override, + factory_cargo_production_override, + farm_mine_cargo_production_override, + named_cargo_production_overrides, cargo_production_overrides, world_scalar_overrides, special_conditions, @@ -1321,6 +1352,10 @@ fn lower_contextual_real_grouped_effects( if real_grouped_row_is_unsupported_chairman_target_scope(row) { return Err(ImportBlocker::ChairmanTargetScope); } + if let Some(effect) = lower_contextual_cargo_price_effect(row)? { + effects.push(effect); + continue; + } if let Some(effect) = lower_contextual_world_scalar_override_effect(row)? { effects.push(effect); continue; @@ -1356,6 +1391,27 @@ fn lower_contextual_real_grouped_effects( Ok(effects) } +fn lower_contextual_cargo_price_effect( + row: &SmpLoadedPackedEventGroupedEffectRowSummary, +) -> Result, ImportBlocker> { + if row.parameter_family.as_deref() != Some("cargo_price_scalar") { + return Ok(None); + } + if row.row_shape != "scalar_assignment" { + return Ok(None); + } + let Some(value) = u32::try_from(row.raw_scalar_value).ok() else { + return Ok(None); + }; + if row.descriptor_id != 105 { + return Ok(None); + } + Ok(Some(RuntimeEffect::SetCargoPriceOverride { + target: RuntimeCargoPriceTarget::All, + value, + })) +} + fn lower_contextual_world_scalar_override_effect( row: &SmpLoadedPackedEventGroupedEffectRowSummary, ) -> Result, ImportBlocker> { @@ -1419,13 +1475,27 @@ fn lower_contextual_cargo_production_effect( let Some(value) = u32::try_from(row.raw_scalar_value).ok() else { return Ok(None); }; - let Some(slot) = row.descriptor_id.checked_sub(229) else { - return Ok(None); - }; - if !(1..=11).contains(&slot) { - return Ok(None); + match row.descriptor_id { + 177 => Ok(Some(RuntimeEffect::SetCargoProductionOverride { + target: RuntimeCargoProductionTarget::All, + value, + })), + 178 => Ok(Some(RuntimeEffect::SetCargoProductionOverride { + target: RuntimeCargoProductionTarget::Factory, + value, + })), + 179 => Ok(Some(RuntimeEffect::SetCargoProductionOverride { + target: RuntimeCargoProductionTarget::FarmMine, + value, + })), + 230..=240 => { + let Some(slot) = row.descriptor_id.checked_sub(229) else { + return Ok(None); + }; + Ok(Some(RuntimeEffect::SetCargoProductionSlot { slot, value })) + } + _ => Ok(None), } - Ok(Some(RuntimeEffect::SetCargoProductionSlot { slot, value })) } fn lower_contextual_territory_access_cost_effect( @@ -1736,6 +1806,18 @@ fn lower_condition_targets_in_effect( value: *value, } } + RuntimeEffect::SetCargoPriceOverride { target, value } => { + RuntimeEffect::SetCargoPriceOverride { + target: target.clone(), + value: *value, + } + } + RuntimeEffect::SetCargoProductionOverride { target, value } => { + RuntimeEffect::SetCargoProductionOverride { + target: target.clone(), + value: *value, + } + } RuntimeEffect::SetCargoProductionSlot { slot, value } => { RuntimeEffect::SetCargoProductionSlot { slot: *slot, @@ -2383,6 +2465,18 @@ fn smp_runtime_effect_to_runtime_effect( value: *value, }) } + RuntimeEffect::SetCargoPriceOverride { target, value } => { + Ok(RuntimeEffect::SetCargoPriceOverride { + target: target.clone(), + value: *value, + }) + } + RuntimeEffect::SetCargoProductionOverride { target, value } => { + Ok(RuntimeEffect::SetCargoProductionOverride { + target: target.clone(), + value: *value, + }) + } RuntimeEffect::SetWorldScalarOverride { key, value } => { Ok(RuntimeEffect::SetWorldScalarOverride { key: key.clone(), @@ -2888,10 +2982,21 @@ fn real_grouped_row_is_unsupported_executable_descriptor_variant( 8 | 108 | 109 | 122 => row.row_shape != "scalar_assignment", 13 | 14 => !(row.row_shape == "bool_toggle" && row.raw_scalar_value != 0), 56 | 57 => row.row_shape != "scalar_assignment", - _ => { - row.parameter_family.as_deref() == Some("world_scalar_override") - && row.row_shape != "scalar_assignment" - } + _ => match row.parameter_family.as_deref() { + Some("world_scalar_override") => row.row_shape != "scalar_assignment", + Some("cargo_price_scalar") => { + row.descriptor_id == 105 + && !(row.row_shape == "scalar_assignment" && row.raw_scalar_value >= 0) + } + Some("cargo_production_scalar") => { + matches!(row.descriptor_id, 177 | 178 | 179 | 230..=240) + && !(row.row_shape == "scalar_assignment" && row.raw_scalar_value >= 0) + } + Some("territory_access_cost_scalar") => { + !(row.row_shape == "scalar_assignment" && row.raw_scalar_value >= 0) + } + _ => false, + }, } } @@ -3093,6 +3198,8 @@ fn runtime_effect_uses_condition_true_company(effect: &RuntimeEffect) -> bool { | RuntimeEffect::SetNamedLocomotiveAvailability { .. } | RuntimeEffect::SetNamedLocomotiveAvailabilityValue { .. } | RuntimeEffect::SetNamedLocomotiveCost { .. } + | RuntimeEffect::SetCargoPriceOverride { .. } + | RuntimeEffect::SetCargoProductionOverride { .. } | RuntimeEffect::SetCargoProductionSlot { .. } | RuntimeEffect::SetTerritoryAccessCost { .. } | RuntimeEffect::SetSpecialCondition { .. } @@ -3203,6 +3310,8 @@ fn runtime_effect_company_target_import_blocker( | RuntimeEffect::SetNamedLocomotiveAvailability { .. } | RuntimeEffect::SetNamedLocomotiveAvailabilityValue { .. } | RuntimeEffect::SetNamedLocomotiveCost { .. } + | RuntimeEffect::SetCargoPriceOverride { .. } + | RuntimeEffect::SetCargoProductionOverride { .. } | RuntimeEffect::SetCargoProductionSlot { .. } | RuntimeEffect::SetTerritoryAccessCost { .. } | RuntimeEffect::SetSpecialCondition { .. } @@ -3552,6 +3661,12 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), @@ -4299,6 +4414,145 @@ mod tests { } } + fn real_all_cargo_price_row(value: i32) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary { + crate::SmpLoadedPackedEventGroupedEffectRowSummary { + group_index: 0, + row_index: 0, + descriptor_id: 105, + descriptor_label: Some("All Cargo Prices".to_string()), + target_mask_bits: Some(0x08), + parameter_family: Some("cargo_price_scalar".to_string()), + grouped_target_subject: None, + grouped_target_scope: None, + opcode: 3, + raw_scalar_value: value, + 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".to_string(), + semantic_family: Some("scalar_assignment".to_string()), + semantic_preview: Some(format!("Set All Cargo Prices to {value}")), + recovered_cargo_slot: None, + recovered_cargo_class: None, + recovered_locomotive_id: None, + locomotive_name: None, + notes: vec![ + "descriptor recovered from checked-in EventEffects semantic catalog".to_string(), + ], + } + } + + fn real_named_cargo_price_row( + descriptor_id: u32, + value: i32, + ) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary { + crate::SmpLoadedPackedEventGroupedEffectRowSummary { + group_index: 0, + row_index: 0, + descriptor_id, + descriptor_label: Some("Unknown Cargo Price".to_string()), + target_mask_bits: Some(0x08), + parameter_family: Some("cargo_price_scalar".to_string()), + grouped_target_subject: None, + grouped_target_scope: None, + opcode: 3, + raw_scalar_value: value, + 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".to_string(), + semantic_family: Some("scalar_assignment".to_string()), + semantic_preview: Some(format!("Set Unknown Cargo Price to {value}")), + recovered_cargo_slot: None, + recovered_cargo_class: None, + recovered_locomotive_id: None, + locomotive_name: None, + notes: vec![ + "descriptor recovered from checked-in EventEffects semantic catalog".to_string(), + ], + } + } + + fn real_aggregate_cargo_production_row( + descriptor_id: u32, + value: i32, + ) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary { + let (label, recovered_cargo_class) = match descriptor_id { + 177 => ("All Cargo Production", None), + 178 => ("All Factory Production", Some("factory".to_string())), + 179 => ("All Farm/Mine Production", Some("farm_mine".to_string())), + _ => ("Unknown Cargo Production", None), + }; + crate::SmpLoadedPackedEventGroupedEffectRowSummary { + group_index: 0, + row_index: 0, + descriptor_id, + descriptor_label: Some(label.to_string()), + target_mask_bits: Some(0x08), + parameter_family: Some("cargo_production_scalar".to_string()), + grouped_target_subject: None, + grouped_target_scope: None, + opcode: 3, + raw_scalar_value: value, + value_byte_0x09: 0, + value_dword_0x0d: 0, + value_byte_0x11: 0, + value_byte_0x12: 0, + value_word_0x14: 0, + value_word_0x16: 0, + row_shape: "scalar_assignment".to_string(), + semantic_family: Some("scalar_assignment".to_string()), + semantic_preview: Some(format!("Set {label} to {value}")), + recovered_cargo_slot: None, + recovered_cargo_class, + recovered_locomotive_id: None, + locomotive_name: None, + notes: vec![ + "descriptor recovered from checked-in EventEffects semantic catalog".to_string(), + ], + } + } + + fn real_named_cargo_production_row( + descriptor_id: u32, + value: i32, + ) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary { + crate::SmpLoadedPackedEventGroupedEffectRowSummary { + group_index: 0, + row_index: 0, + descriptor_id, + descriptor_label: Some("Unknown Cargo Production".to_string()), + target_mask_bits: Some(0x08), + parameter_family: Some("cargo_production_scalar".to_string()), + grouped_target_subject: None, + grouped_target_scope: None, + opcode: 3, + raw_scalar_value: value, + value_byte_0x09: 0, + value_dword_0x0d: 0, + value_byte_0x11: 0, + value_byte_0x12: 0, + value_word_0x14: 0, + value_word_0x16: 0, + row_shape: "scalar_assignment".to_string(), + semantic_family: Some("scalar_assignment".to_string()), + semantic_preview: Some(format!("Set Unknown Cargo Production to {value}")), + recovered_cargo_slot: None, + recovered_cargo_class: None, + recovered_locomotive_id: None, + locomotive_name: None, + notes: vec![ + "descriptor recovered from checked-in EventEffects semantic catalog".to_string(), + ], + } + } + fn real_territory_access_cost_row( value: i32, ) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary { @@ -6738,6 +6992,12 @@ mod tests { ("Locomotive 112".to_string(), 1), ]), named_locomotive_cost: BTreeMap::new(), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), @@ -7144,6 +7404,12 @@ mod tests { ("Locomotive 1".to_string(), 100000), ("Locomotive 101".to_string(), 200000), ]), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), @@ -7409,6 +7675,337 @@ mod tests { assert_eq!(import.state.cargo_production_overrides.get(&1), Some(&125)); } + #[test] + fn imports_aggregate_cargo_economics_rows_into_runtime_records() { + let save_slice = SmpLoadedSaveSlice { + file_extension_hint: Some("gms".to_string()), + container_profile_family: Some("rt3-classic-save-container-v1".to_string()), + mechanism_family: "classic-save-rehydrate-v1".to_string(), + mechanism_confidence: "grounded".to_string(), + trailer_family: None, + bridge_family: None, + profile: None, + candidate_availability_table: None, + named_locomotive_availability_table: None, + locomotive_catalog: None, + cargo_catalog: None, + company_roster: None, + chairman_profile_table: None, + special_conditions_table: None, + event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { + 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()), + metadata_tag_offset: 0x7100, + records_tag_offset: 0x7200, + close_tag_offset: 0x7600, + packed_state_version: 0x3e9, + packed_state_version_hex: "0x000003e9".to_string(), + live_id_bound: 38, + live_record_count: 1, + live_entry_ids: vec![38], + decoded_record_count: 1, + imported_runtime_record_count: 0, + records: vec![crate::SmpLoadedPackedEventRecordSummary { + record_index: 0, + live_entry_id: 38, + payload_offset: Some(0x7202), + payload_len: Some(144), + 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: Some(false), + compact_control: Some(real_compact_control()), + text_bands: vec![], + standalone_condition_row_count: 0, + standalone_condition_rows: vec![], + negative_sentinel_scope: None, + grouped_effect_row_counts: vec![4, 0, 0, 0], + grouped_effect_rows: vec![ + real_all_cargo_price_row(180), + real_aggregate_cargo_production_row(177, 210), + real_aggregate_cargo_production_row(178, 225), + real_aggregate_cargo_production_row(179, 175), + ], + decoded_conditions: Vec::new(), + decoded_actions: vec![], + executable_import_ready: false, + notes: vec![ + "grounded aggregate cargo economics descriptors import through bounded override surfaces" + .to_string(), + ], + }], + }), + notes: vec![], + }; + + let mut import = project_save_slice_to_runtime_state_import( + &save_slice, + "packed-events-aggregate-cargo-economics", + None, + ) + .expect("save slice should project"); + + assert_eq!(import.state.event_runtime_records.len(), 1); + assert_eq!( + import + .state + .packed_event_collection + .as_ref() + .and_then(|summary| summary.records[0].import_outcome.as_deref()), + Some("imported") + ); + + execute_step_command( + &mut import.state, + &StepCommand::ServiceTriggerKind { trigger_kind: 7 }, + ) + .expect("aggregate cargo economics runtime record should run"); + + assert_eq!(import.state.all_cargo_price_override, Some(180)); + assert_eq!(import.state.all_cargo_production_override, Some(210)); + assert_eq!(import.state.factory_cargo_production_override, Some(225)); + assert_eq!(import.state.farm_mine_cargo_production_override, Some(175)); + } + + #[test] + fn keeps_named_cargo_price_rows_evidence_blocked() { + let save_slice = SmpLoadedSaveSlice { + file_extension_hint: Some("gms".to_string()), + container_profile_family: Some("rt3-classic-save-container-v1".to_string()), + mechanism_family: "classic-save-rehydrate-v1".to_string(), + mechanism_confidence: "grounded".to_string(), + trailer_family: None, + bridge_family: None, + profile: None, + candidate_availability_table: None, + named_locomotive_availability_table: None, + locomotive_catalog: None, + cargo_catalog: None, + company_roster: None, + chairman_profile_table: None, + special_conditions_table: None, + event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { + 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()), + metadata_tag_offset: 0x7100, + records_tag_offset: 0x7200, + close_tag_offset: 0x7600, + packed_state_version: 0x3e9, + packed_state_version_hex: "0x000003e9".to_string(), + live_id_bound: 39, + live_record_count: 1, + live_entry_ids: vec![39], + decoded_record_count: 1, + imported_runtime_record_count: 0, + records: vec![crate::SmpLoadedPackedEventRecordSummary { + record_index: 0, + live_entry_id: 39, + 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: Some(false), + compact_control: Some(real_compact_control()), + text_bands: vec![], + standalone_condition_row_count: 0, + standalone_condition_rows: vec![], + negative_sentinel_scope: None, + grouped_effect_row_counts: vec![1, 0, 0, 0], + grouped_effect_rows: vec![real_named_cargo_price_row(106, 140)], + decoded_conditions: Vec::new(), + decoded_actions: vec![], + executable_import_ready: false, + notes: vec!["named cargo price descriptors remain evidence-blocked until cargo ordering is pinned" + .to_string()], + }], + }), + notes: vec![], + }; + + let import = project_save_slice_to_runtime_state_import( + &save_slice, + "packed-events-named-cargo-price-parity", + None, + ) + .expect("save slice should project"); + + assert!(import.state.event_runtime_records.is_empty()); + assert_eq!( + import + .state + .packed_event_collection + .as_ref() + .and_then(|summary| summary.records[0].import_outcome.as_deref()), + Some("blocked_evidence_blocked_descriptor") + ); + } + + #[test] + fn keeps_named_cargo_production_rows_evidence_blocked() { + let save_slice = SmpLoadedSaveSlice { + file_extension_hint: Some("gms".to_string()), + container_profile_family: Some("rt3-classic-save-container-v1".to_string()), + mechanism_family: "classic-save-rehydrate-v1".to_string(), + mechanism_confidence: "grounded".to_string(), + trailer_family: None, + bridge_family: None, + profile: None, + candidate_availability_table: None, + named_locomotive_availability_table: None, + locomotive_catalog: None, + cargo_catalog: None, + company_roster: None, + chairman_profile_table: None, + special_conditions_table: None, + event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { + 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()), + metadata_tag_offset: 0x7100, + records_tag_offset: 0x7200, + close_tag_offset: 0x7600, + packed_state_version: 0x3e9, + packed_state_version_hex: "0x000003e9".to_string(), + live_id_bound: 40, + live_record_count: 1, + live_entry_ids: vec![40], + decoded_record_count: 1, + imported_runtime_record_count: 0, + records: vec![crate::SmpLoadedPackedEventRecordSummary { + record_index: 0, + live_entry_id: 40, + 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: Some(false), + compact_control: Some(real_compact_control()), + text_bands: vec![], + standalone_condition_row_count: 0, + standalone_condition_rows: vec![], + negative_sentinel_scope: None, + grouped_effect_row_counts: vec![1, 0, 0, 0], + grouped_effect_rows: vec![real_named_cargo_production_row(180, 160)], + decoded_conditions: Vec::new(), + decoded_actions: vec![], + executable_import_ready: false, + notes: vec!["named cargo production descriptors remain evidence-blocked until cargo ordering is pinned" + .to_string()], + }], + }), + notes: vec![], + }; + + let import = project_save_slice_to_runtime_state_import( + &save_slice, + "packed-events-named-cargo-production-parity", + None, + ) + .expect("save slice should project"); + + assert!(import.state.event_runtime_records.is_empty()); + assert_eq!( + import + .state + .packed_event_collection + .as_ref() + .and_then(|summary| summary.records[0].import_outcome.as_deref()), + Some("blocked_evidence_blocked_descriptor") + ); + } + + #[test] + fn keeps_negative_all_cargo_price_rows_variant_blocked() { + let save_slice = SmpLoadedSaveSlice { + file_extension_hint: Some("gms".to_string()), + container_profile_family: Some("rt3-classic-save-container-v1".to_string()), + mechanism_family: "classic-save-rehydrate-v1".to_string(), + mechanism_confidence: "grounded".to_string(), + trailer_family: None, + bridge_family: None, + profile: None, + candidate_availability_table: None, + named_locomotive_availability_table: None, + locomotive_catalog: None, + cargo_catalog: None, + company_roster: None, + chairman_profile_table: None, + special_conditions_table: None, + event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary { + 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()), + metadata_tag_offset: 0x7100, + records_tag_offset: 0x7200, + close_tag_offset: 0x7600, + packed_state_version: 0x3e9, + packed_state_version_hex: "0x000003e9".to_string(), + live_id_bound: 41, + live_record_count: 1, + live_entry_ids: vec![41], + decoded_record_count: 1, + imported_runtime_record_count: 0, + records: vec![crate::SmpLoadedPackedEventRecordSummary { + record_index: 0, + live_entry_id: 41, + 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: Some(false), + compact_control: Some(real_compact_control()), + text_bands: vec![], + standalone_condition_row_count: 0, + standalone_condition_rows: vec![], + negative_sentinel_scope: None, + grouped_effect_row_counts: vec![1, 0, 0, 0], + grouped_effect_rows: vec![real_all_cargo_price_row(-1)], + decoded_conditions: Vec::new(), + decoded_actions: vec![], + executable_import_ready: false, + notes: vec![ + "negative aggregate cargo price variants remain parity-only".to_string(), + ], + }], + }), + notes: vec![], + }; + + let import = project_save_slice_to_runtime_state_import( + &save_slice, + "packed-events-negative-all-cargo-price", + None, + ) + .expect("save slice should project"); + + assert!(import.state.event_runtime_records.is_empty()); + assert_eq!( + import + .state + .packed_event_collection + .as_ref() + .and_then(|summary| summary.records[0].import_outcome.as_deref()), + Some("blocked_variant_or_scope_blocked_descriptor") + ); + } + #[test] fn imports_recovered_territory_access_cost_rows_into_runtime_records() { let save_slice = SmpLoadedSaveSlice { @@ -7545,6 +8142,12 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), @@ -9820,6 +10423,12 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), @@ -9994,6 +10603,12 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), @@ -11683,6 +12298,12 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), @@ -11868,6 +12489,12 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), diff --git a/crates/rrt-runtime/src/lib.rs b/crates/rrt-runtime/src/lib.rs index 2f457bf..38bdca5 100644 --- a/crates/rrt-runtime/src/lib.rs +++ b/crates/rrt-runtime/src/lib.rs @@ -35,7 +35,8 @@ pub use pk4::{ extract_pk4_entry_bytes, extract_pk4_entry_file, inspect_pk4_bytes, inspect_pk4_file, }; pub use runtime::{ - RuntimeCargoCatalogEntry, RuntimeCargoClass, RuntimeChairmanMetric, RuntimeChairmanProfile, + RuntimeCargoCatalogEntry, RuntimeCargoClass, RuntimeCargoPriceTarget, + RuntimeCargoProductionTarget, RuntimeChairmanMetric, RuntimeChairmanProfile, RuntimeChairmanTarget, RuntimeCompany, RuntimeCompanyConditionTestScope, RuntimeCompanyControllerKind, RuntimeCompanyMetric, RuntimeCompanyTarget, RuntimeCompanyTerritoryAccess, RuntimeCompanyTerritoryTrackPieceCount, RuntimeCondition, diff --git a/crates/rrt-runtime/src/persistence.rs b/crates/rrt-runtime/src/persistence.rs index ca288f3..0b66854 100644 --- a/crates/rrt-runtime/src/persistence.rs +++ b/crates/rrt-runtime/src/persistence.rs @@ -109,6 +109,12 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), diff --git a/crates/rrt-runtime/src/runtime.rs b/crates/rrt-runtime/src/runtime.rs index 30879fd..8c16fd9 100644 --- a/crates/rrt-runtime/src/runtime.rs +++ b/crates/rrt-runtime/src/runtime.rs @@ -203,6 +203,22 @@ pub enum RuntimeChairmanTarget { Ids { ids: Vec }, } +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(tag = "kind", rename_all = "snake_case")] +pub enum RuntimeCargoPriceTarget { + All, + Named { name: String }, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(tag = "kind", rename_all = "snake_case")] +pub enum RuntimeCargoProductionTarget { + All, + Factory, + FarmMine, + Named { name: String }, +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum RuntimeTerritoryTarget { @@ -463,6 +479,14 @@ pub enum RuntimeEffect { name: String, value: u32, }, + SetCargoPriceOverride { + target: RuntimeCargoPriceTarget, + value: u32, + }, + SetCargoProductionOverride { + target: RuntimeCargoProductionTarget, + value: u32, + }, SetCargoProductionSlot { slot: u32, value: u32, @@ -844,6 +868,18 @@ pub struct RuntimeState { #[serde(default)] pub named_locomotive_cost: BTreeMap, #[serde(default)] + pub all_cargo_price_override: Option, + #[serde(default)] + pub named_cargo_price_overrides: BTreeMap, + #[serde(default)] + pub all_cargo_production_override: Option, + #[serde(default)] + pub factory_cargo_production_override: Option, + #[serde(default)] + pub farm_mine_cargo_production_override: Option, + #[serde(default)] + pub named_cargo_production_overrides: BTreeMap, + #[serde(default)] pub cargo_production_overrides: BTreeMap, #[serde(default)] pub world_scalar_overrides: BTreeMap, @@ -1487,6 +1523,16 @@ impl RuntimeState { return Err("named_locomotive_cost contains an empty key".to_string()); } } + for key in self.named_cargo_price_overrides.keys() { + if key.trim().is_empty() { + return Err("named_cargo_price_overrides contains an empty key".to_string()); + } + } + for key in self.named_cargo_production_overrides.keys() { + if key.trim().is_empty() { + return Err("named_cargo_production_overrides contains an empty key".to_string()); + } + } for slot in self.cargo_production_overrides.keys() { if !(1..=11).contains(slot) { return Err(format!( @@ -1600,6 +1646,24 @@ fn validate_runtime_effect( return Err("name must not be empty".to_string()); } } + RuntimeEffect::SetCargoPriceOverride { target, .. } => match target { + RuntimeCargoPriceTarget::All => {} + RuntimeCargoPriceTarget::Named { name } => { + if name.trim().is_empty() { + return Err("name must not be empty".to_string()); + } + } + }, + RuntimeEffect::SetCargoProductionOverride { target, .. } => match target { + RuntimeCargoProductionTarget::All + | RuntimeCargoProductionTarget::Factory + | RuntimeCargoProductionTarget::FarmMine => {} + RuntimeCargoProductionTarget::Named { name } => { + if name.trim().is_empty() { + return Err("name must not be empty".to_string()); + } + } + }, RuntimeEffect::SetCargoProductionSlot { slot, .. } => { if !(1..=11).contains(slot) { return Err("slot must be in 1..=11".to_string()); @@ -1916,6 +1980,12 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), @@ -1979,7 +2049,13 @@ mod tests { event_runtime_records: Vec::new(), candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), - named_locomotive_cost: BTreeMap::new(), + named_locomotive_cost: BTreeMap::new(), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), @@ -2048,6 +2124,12 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), @@ -2126,6 +2208,12 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), @@ -2229,6 +2317,12 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), @@ -2284,6 +2378,12 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), @@ -2339,6 +2439,12 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), @@ -2411,6 +2517,12 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), @@ -2473,6 +2585,12 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), @@ -2539,6 +2657,12 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), @@ -2601,6 +2725,12 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), @@ -2669,6 +2799,12 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), @@ -2731,6 +2867,12 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), @@ -2793,6 +2935,12 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), @@ -2848,6 +2996,12 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), @@ -2913,6 +3067,12 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), diff --git a/crates/rrt-runtime/src/smp.rs b/crates/rrt-runtime/src/smp.rs index 8e4cc64..40e7fec 100644 --- a/crates/rrt-runtime/src/smp.rs +++ b/crates/rrt-runtime/src/smp.rs @@ -7,11 +7,12 @@ use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use crate::{ - RuntimeCargoClass, RuntimeChairmanMetric, RuntimeChairmanTarget, - RuntimeCompanyConditionTestScope, RuntimeCompanyControllerKind, RuntimeCompanyMetric, - RuntimeCompanyTarget, RuntimeCondition, RuntimeConditionComparator, RuntimeEffect, - RuntimeEventRecordTemplate, RuntimePlayerConditionTestScope, RuntimePlayerTarget, - RuntimeTerritoryMetric, RuntimeTerritoryTarget, RuntimeTrackMetric, RuntimeTrackPieceCounts, + RuntimeCargoClass, RuntimeCargoPriceTarget, RuntimeCargoProductionTarget, + RuntimeChairmanMetric, RuntimeChairmanTarget, RuntimeCompanyConditionTestScope, + RuntimeCompanyControllerKind, RuntimeCompanyMetric, RuntimeCompanyTarget, RuntimeCondition, + RuntimeConditionComparator, RuntimeEffect, RuntimeEventRecordTemplate, + RuntimePlayerConditionTestScope, RuntimePlayerTarget, RuntimeTerritoryMetric, + RuntimeTerritoryTarget, RuntimeTrackMetric, RuntimeTrackPieceCounts, }; pub const SMP_FOUR_SIDECAR_BYTE_PLANES_MIN_BUNDLE_VERSION: u32 = 0x03ec; @@ -3656,7 +3657,9 @@ fn real_condition_chairman_target( fn real_grouped_effect_descriptor_metadata( descriptor_id: u32, ) -> Option { - recovered_cargo_production_descriptor_metadata(descriptor_id) + recovered_cargo_price_descriptor_metadata(descriptor_id) + .or_else(|| recovered_cargo_economics_descriptor_metadata(descriptor_id)) + .or_else(|| recovered_cargo_production_descriptor_metadata(descriptor_id)) .or_else(|| recovered_locomotive_availability_descriptor_metadata(descriptor_id)) .or_else(|| recovered_locomotive_cost_descriptor_metadata(descriptor_id)) .or_else(|| recovered_territory_access_cost_descriptor_metadata(descriptor_id)) @@ -3676,6 +3679,55 @@ fn real_grouped_effect_descriptor_metadata( }) } +fn recovered_cargo_price_descriptor_metadata( + descriptor_id: u32, +) -> Option { + (descriptor_id == 105).then_some(RealGroupedEffectDescriptorMetadata { + descriptor_id, + label: "All Cargo Prices", + target_mask_bits: 0x08, + parameter_family: "cargo_price_scalar", + runtime_key: None, + runtime_status: RealGroupedEffectRuntimeStatus::Executable, + executable_in_runtime: true, + }) +} + +fn recovered_cargo_economics_descriptor_metadata( + descriptor_id: u32, +) -> Option { + match descriptor_id { + 177 => Some(RealGroupedEffectDescriptorMetadata { + descriptor_id, + label: "All Cargo Production", + target_mask_bits: 0x08, + parameter_family: "cargo_production_scalar", + runtime_key: None, + runtime_status: RealGroupedEffectRuntimeStatus::Executable, + executable_in_runtime: true, + }), + 178 => Some(RealGroupedEffectDescriptorMetadata { + descriptor_id, + label: "All Factory Production", + target_mask_bits: 0x08, + parameter_family: "cargo_production_scalar", + runtime_key: None, + runtime_status: RealGroupedEffectRuntimeStatus::Executable, + executable_in_runtime: true, + }), + 179 => Some(RealGroupedEffectDescriptorMetadata { + descriptor_id, + label: "All Farm/Mine Production", + target_mask_bits: 0x08, + parameter_family: "cargo_production_scalar", + runtime_key: None, + runtime_status: RealGroupedEffectRuntimeStatus::Executable, + executable_in_runtime: true, + }), + _ => None, + } +} + fn recovered_cargo_production_descriptor_metadata( descriptor_id: u32, ) -> Option { @@ -4260,16 +4312,45 @@ fn decode_real_grouped_effect_action( }); } + if descriptor_metadata.executable_in_runtime + && descriptor_metadata.parameter_family == "cargo_price_scalar" + && row.row_shape == "scalar_assignment" + && row.raw_scalar_value >= 0 + && row.descriptor_id == 105 + { + return Some(RuntimeEffect::SetCargoPriceOverride { + target: RuntimeCargoPriceTarget::All, + value: row.raw_scalar_value as u32, + }); + } + if descriptor_metadata.executable_in_runtime && descriptor_metadata.parameter_family == "cargo_production_scalar" && row.row_shape == "scalar_assignment" && row.raw_scalar_value >= 0 { - let slot = descriptor_metadata.descriptor_id.checked_sub(229)?; - return Some(RuntimeEffect::SetCargoProductionSlot { - slot, - value: row.raw_scalar_value as u32, - }); + return match descriptor_metadata.descriptor_id { + 177 => Some(RuntimeEffect::SetCargoProductionOverride { + target: RuntimeCargoProductionTarget::All, + value: row.raw_scalar_value as u32, + }), + 178 => Some(RuntimeEffect::SetCargoProductionOverride { + target: RuntimeCargoProductionTarget::Factory, + value: row.raw_scalar_value as u32, + }), + 179 => Some(RuntimeEffect::SetCargoProductionOverride { + target: RuntimeCargoProductionTarget::FarmMine, + value: row.raw_scalar_value as u32, + }), + 230..=240 => { + let slot = descriptor_metadata.descriptor_id.checked_sub(229)?; + Some(RuntimeEffect::SetCargoProductionSlot { + slot, + value: row.raw_scalar_value as u32, + }) + } + _ => None, + }; } if descriptor_metadata.executable_in_runtime @@ -4563,6 +4644,8 @@ fn runtime_effect_supported_for_save_import(effect: &RuntimeEffect) -> bool { | RuntimeEffect::SetNamedLocomotiveAvailability { .. } | RuntimeEffect::SetNamedLocomotiveAvailabilityValue { .. } | RuntimeEffect::SetNamedLocomotiveCost { .. } + | RuntimeEffect::SetCargoPriceOverride { .. } + | RuntimeEffect::SetCargoProductionOverride { .. } | RuntimeEffect::SetCargoProductionSlot { .. } | RuntimeEffect::SetWorldScalarOverride { .. } | RuntimeEffect::SetTerritoryAccessCost { .. } @@ -11248,6 +11331,30 @@ mod tests { assert!(metadata.executable_in_runtime); } + #[test] + fn looks_up_recovered_all_cargo_price_descriptor_metadata() { + let metadata = + real_grouped_effect_descriptor_metadata(105).expect("descriptor metadata should exist"); + + assert_eq!(metadata.label, "All Cargo Prices"); + assert_eq!(metadata.target_mask_bits, 0x08); + assert_eq!(metadata.parameter_family, "cargo_price_scalar"); + assert_eq!(metadata.runtime_key, None); + assert!(metadata.executable_in_runtime); + } + + #[test] + fn looks_up_recovered_aggregate_cargo_production_descriptor_metadata() { + let metadata = + real_grouped_effect_descriptor_metadata(177).expect("descriptor metadata should exist"); + + assert_eq!(metadata.label, "All Cargo Production"); + assert_eq!(metadata.target_mask_bits, 0x08); + assert_eq!(metadata.parameter_family, "cargo_production_scalar"); + assert_eq!(metadata.runtime_key, None); + assert!(metadata.executable_in_runtime); + } + #[test] fn looks_up_recovered_lower_band_locomotive_cost_descriptor_metadata() { let metadata = diff --git a/crates/rrt-runtime/src/step.rs b/crates/rrt-runtime/src/step.rs index ea8f8ec..b728af5 100644 --- a/crates/rrt-runtime/src/step.rs +++ b/crates/rrt-runtime/src/step.rs @@ -3,7 +3,8 @@ use std::collections::BTreeSet; use serde::{Deserialize, Serialize}; use crate::{ - RuntimeCargoClass, RuntimeChairmanMetric, RuntimeChairmanTarget, RuntimeCompanyControllerKind, + RuntimeCargoClass, RuntimeCargoPriceTarget, RuntimeCargoProductionTarget, + RuntimeChairmanMetric, RuntimeChairmanTarget, RuntimeCompanyControllerKind, RuntimeCompanyMetric, RuntimeCompanyTarget, RuntimeCondition, RuntimeConditionComparator, RuntimeEffect, RuntimeEventRecordTemplate, RuntimePlayerTarget, RuntimeState, RuntimeSummary, RuntimeTerritoryMetric, RuntimeTerritoryTarget, RuntimeTrackMetric, RuntimeTrackPieceCounts, @@ -606,6 +607,32 @@ fn apply_runtime_effects( RuntimeEffect::SetNamedLocomotiveCost { name, value } => { state.named_locomotive_cost.insert(name.clone(), *value); } + RuntimeEffect::SetCargoPriceOverride { target, value } => match target { + RuntimeCargoPriceTarget::All => { + state.all_cargo_price_override = Some(*value); + } + RuntimeCargoPriceTarget::Named { name } => { + state + .named_cargo_price_overrides + .insert(name.clone(), *value); + } + }, + RuntimeEffect::SetCargoProductionOverride { target, value } => match target { + RuntimeCargoProductionTarget::All => { + state.all_cargo_production_override = Some(*value); + } + RuntimeCargoProductionTarget::Factory => { + state.factory_cargo_production_override = Some(*value); + } + RuntimeCargoProductionTarget::FarmMine => { + state.farm_mine_cargo_production_override = Some(*value); + } + RuntimeCargoProductionTarget::Named { name } => { + state + .named_cargo_production_overrides + .insert(name.clone(), *value); + } + }, RuntimeEffect::SetCargoProductionSlot { slot, value } => { state.cargo_production_overrides.insert(*slot, *value); } @@ -1509,8 +1536,9 @@ mod tests { use super::*; use crate::{ - CalendarPoint, RuntimeChairmanMetric, RuntimeChairmanProfile, RuntimeChairmanTarget, - RuntimeCompany, RuntimeCompanyControllerKind, RuntimeCompanyTarget, RuntimeCondition, + CalendarPoint, RuntimeCargoPriceTarget, RuntimeCargoProductionTarget, + RuntimeChairmanMetric, RuntimeChairmanProfile, RuntimeChairmanTarget, RuntimeCompany, + RuntimeCompanyControllerKind, RuntimeCompanyTarget, RuntimeCondition, RuntimeConditionComparator, RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate, RuntimePlayer, RuntimeSaveProfileState, RuntimeServiceState, RuntimeTerritory, RuntimeTerritoryTarget, RuntimeTrackPieceCounts, RuntimeTrain, RuntimeWorldRestoreState, @@ -1561,6 +1589,12 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), @@ -1920,6 +1954,26 @@ mod tests { key: "world.build_stations_cost".to_string(), value: 350000, }, + RuntimeEffect::SetCargoPriceOverride { + target: RuntimeCargoPriceTarget::All, + value: 180, + }, + RuntimeEffect::SetCargoPriceOverride { + target: RuntimeCargoPriceTarget::Named { + name: "Coal".to_string(), + }, + value: 95, + }, + RuntimeEffect::SetCargoProductionOverride { + target: RuntimeCargoProductionTarget::Factory, + value: 225, + }, + RuntimeEffect::SetCargoProductionOverride { + target: RuntimeCargoProductionTarget::Named { + name: "Corn".to_string(), + }, + value: 140, + }, RuntimeEffect::SetCargoProductionSlot { slot: 1, value: 125, @@ -1942,9 +1996,16 @@ mod tests { .get("world.build_stations_cost"), Some(&350000) ); + assert_eq!(state.all_cargo_price_override, Some(180)); + assert_eq!(state.named_cargo_price_overrides.get("Coal"), Some(&95)); + assert_eq!(state.factory_cargo_production_override, Some(225)); + assert_eq!( + state.named_cargo_production_overrides.get("Corn"), + Some(&140) + ); assert_eq!(state.cargo_production_overrides.get(&1), Some(&125)); assert_eq!(state.world_restore.territory_access_cost, Some(750000)); - assert_eq!(result.service_events[0].applied_effect_count, 3); + assert_eq!(result.service_events[0].applied_effect_count, 7); } #[test] diff --git a/crates/rrt-runtime/src/summary.rs b/crates/rrt-runtime/src/summary.rs index f9dec07..d676d61 100644 --- a/crates/rrt-runtime/src/summary.rs +++ b/crates/rrt-runtime/src/summary.rs @@ -934,6 +934,12 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), @@ -1051,6 +1057,12 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), @@ -1105,6 +1117,12 @@ mod tests { ("Mikado".to_string(), 0), ]), named_locomotive_cost: BTreeMap::new(), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), @@ -1151,6 +1169,12 @@ mod tests { ("Big Boy".to_string(), 250000), ("GP7".to_string(), 175000), ]), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), @@ -1195,6 +1219,12 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::from([(1, 125), (2, 250)]), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), @@ -1301,6 +1331,12 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), @@ -1385,6 +1421,12 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), @@ -1465,6 +1507,12 @@ mod tests { candidate_availability: BTreeMap::new(), named_locomotive_availability: BTreeMap::new(), named_locomotive_cost: BTreeMap::new(), + all_cargo_price_override: None, + named_cargo_price_overrides: BTreeMap::new(), + all_cargo_production_override: None, + factory_cargo_production_override: None, + farm_mine_cargo_production_override: None, + named_cargo_production_overrides: BTreeMap::new(), cargo_production_overrides: BTreeMap::new(), world_scalar_overrides: BTreeMap::new(), special_conditions: BTreeMap::new(), diff --git a/docs/README.md b/docs/README.md index 9cc2365..c9cb73d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -113,6 +113,11 @@ The highest-value next passes are now: landing surface too: representative rows execute into `RuntimeState.world_scalar_overrides` through stable normalized keys such as `world.build_stations_cost` and `world.track_maintenance_cost` +- the grounded aggregate cargo-economics descriptors now execute too: descriptor `105` + `All Cargo Prices` and descriptors `177..179` `All Cargo Production` / `All Factory Production` + / `All Farm/Mine Production` land on bounded event-owned cargo override state, while the named + cargo-price and named cargo-production strips remain explicit + `blocked_evidence_blocked_descriptor` parity until descriptor ordering is pinned more strongly - 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` diff --git a/docs/runtime-rehost-plan.md b/docs/runtime-rehost-plan.md index b8738be..4d0b5aa 100644 --- a/docs/runtime-rehost-plan.md +++ b/docs/runtime-rehost-plan.md @@ -75,6 +75,11 @@ Implemented today: - the recovered whole-game scalar economy/performance strip `59..104` now has a bounded runtime landing surface too: representative descriptors import as `SetWorldScalarOverride` and land in `RuntimeState.world_scalar_overrides` +- the grounded aggregate cargo-economics descriptors now execute too: descriptor `105` + `All Cargo Prices` and descriptors `177..179` `All Cargo Production` / `All Factory Production` + / `All Farm/Mine Production` import through bounded cargo override surfaces, while the named + cargo-price and named cargo-production strips now sit on explicit + `blocked_evidence_blocked_descriptor` parity instead of generic unmapped-descriptor frontier - a minimal event-owned train surface and an opaque economic-status lane now exist in runtime state, and real descriptors `8` = `Economic Status`, `9` = `Confiscate All`, and `15` = `Retire Train` now import and execute through the ordinary runtime path when overlay context diff --git a/fixtures/runtime/packed-event-cargo-economics-parity-save-slice-fixture.json b/fixtures/runtime/packed-event-cargo-economics-parity-save-slice-fixture.json new file mode 100644 index 0000000..2992bea --- /dev/null +++ b/fixtures/runtime/packed-event-cargo-economics-parity-save-slice-fixture.json @@ -0,0 +1,50 @@ +{ + "format_version": 1, + "fixture_id": "packed-event-cargo-economics-parity-save-slice-fixture", + "source": { + "kind": "captured-runtime", + "description": "Fixture pinning the remaining named cargo-economics descriptor strips on explicit evidence-blocked parity." + }, + "state_save_slice_path": "packed-event-cargo-economics-parity-save-slice.json", + "commands": [ + { + "kind": "step_count", + "steps": 1 + } + ], + "expected_summary": { + "calendar_projection_is_placeholder": true, + "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_blocked_evidence_blocked_descriptor_count": 1, + "packed_event_blocked_unmapped_real_descriptor_count": 0, + "packed_event_blocked_unmapped_world_descriptor_count": 0, + "event_runtime_record_count": 0 + }, + "expected_state_fragment": { + "packed_event_collection": { + "records": [ + { + "import_outcome": "blocked_evidence_blocked_descriptor", + "grouped_effect_rows": [ + { + "descriptor_id": 106, + "descriptor_label": "Unknown Cargo Price", + "parameter_family": "cargo_price_scalar", + "semantic_family": "scalar_assignment" + }, + { + "descriptor_id": 180, + "descriptor_label": "Unknown Cargo Production", + "parameter_family": "cargo_production_scalar", + "semantic_family": "scalar_assignment" + } + ] + } + ] + }, + "event_runtime_records": [] + } +} diff --git a/fixtures/runtime/packed-event-cargo-economics-parity-save-slice.json b/fixtures/runtime/packed-event-cargo-economics-parity-save-slice.json new file mode 100644 index 0000000..5d12397 --- /dev/null +++ b/fixtures/runtime/packed-event-cargo-economics-parity-save-slice.json @@ -0,0 +1,137 @@ +{ + "format_version": 1, + "save_slice_id": "packed-event-cargo-economics-parity-save-slice", + "source": { + "description": "Tracked save-slice document pinning the remaining named cargo-economics descriptor strips on explicit evidence-blocked parity.", + "original_save_filename": "captured-cargo-economics-parity.gms", + "original_save_sha256": "cargo-economics-parity-sample-sha256", + "notes": [ + "tracked as JSON save-slice document rather than raw .smp", + "pins named cargo price and named cargo production rows until exact descriptor-to-cargo ordering is grounded strongly enough" + ] + }, + "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, + "named_locomotive_availability_table": null, + "locomotive_catalog": null, + "cargo_catalog": null, + "company_roster": null, + "chairman_profile_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": 33408, + "records_tag_offset": 33664, + "close_tag_offset": 34176, + "packed_state_version": 1001, + "packed_state_version_hex": "0x000003e9", + "live_id_bound": 65, + "live_record_count": 1, + "live_entry_ids": [65], + "decoded_record_count": 1, + "imported_runtime_record_count": 0, + "records": [ + { + "record_index": 0, + "live_entry_id": 65, + "payload_offset": 33696, + "payload_len": 128, + "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": [], + "grouped_effect_row_counts": [2, 0, 0, 0], + "grouped_effect_rows": [ + { + "group_index": 0, + "row_index": 0, + "descriptor_id": 106, + "descriptor_label": "Unknown Cargo Price", + "target_mask_bits": 8, + "parameter_family": "cargo_price_scalar", + "opcode": 3, + "raw_scalar_value": 140, + "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 Unknown Cargo Price to 140", + "recovered_cargo_slot": null, + "recovered_cargo_class": null, + "recovered_locomotive_id": null, + "locomotive_name": null, + "notes": [ + "descriptor recovered from checked-in EventEffects semantic catalog", + "exact named cargo ordering for the price strip is not yet pinned" + ] + }, + { + "group_index": 0, + "row_index": 1, + "descriptor_id": 180, + "descriptor_label": "Unknown Cargo Production", + "target_mask_bits": 8, + "parameter_family": "cargo_production_scalar", + "opcode": 3, + "raw_scalar_value": 160, + "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 Unknown Cargo Production to 160", + "recovered_cargo_slot": null, + "recovered_cargo_class": null, + "recovered_locomotive_id": null, + "locomotive_name": null, + "notes": [ + "descriptor recovered from checked-in EventEffects semantic catalog", + "exact named cargo ordering for the production strip is not yet pinned" + ] + } + ], + "decoded_actions": [], + "executable_import_ready": false, + "notes": [ + "named cargo-economics descriptor strips remain explicit evidence-blocked parity" + ] + } + ] + }, + "notes": [ + "named cargo economics evidence-blocked parity sample" + ] + } +} diff --git a/fixtures/runtime/packed-event-cargo-economics-save-slice-fixture.json b/fixtures/runtime/packed-event-cargo-economics-save-slice-fixture.json new file mode 100644 index 0000000..355e6a1 --- /dev/null +++ b/fixtures/runtime/packed-event-cargo-economics-save-slice-fixture.json @@ -0,0 +1,75 @@ +{ + "format_version": 1, + "fixture_id": "packed-event-cargo-economics-save-slice-fixture", + "source": { + "kind": "captured-runtime", + "description": "Fixture proving the grounded aggregate cargo-economics descriptors execute through bounded cargo override surfaces." + }, + "state_save_slice_path": "packed-event-cargo-economics-save-slice.json", + "commands": [ + { + "kind": "service_trigger_kind", + "trigger_kind": 7 + } + ], + "expected_summary": { + "calendar_projection_source": "default-1830-placeholder", + "calendar_projection_is_placeholder": true, + "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, + "total_event_record_service_count": 1, + "total_trigger_dispatch_count": 1 + }, + "expected_state_fragment": { + "all_cargo_price_override": 180, + "all_cargo_production_override": 210, + "factory_cargo_production_override": 225, + "farm_mine_cargo_production_override": 175, + "packed_event_collection": { + "records": [ + { + "import_outcome": "imported" + } + ] + }, + "event_runtime_records": [ + { + "record_id": 64, + "service_count": 1, + "effects": [ + { + "kind": "set_cargo_price_override", + "target": { + "kind": "all" + }, + "value": 180 + }, + { + "kind": "set_cargo_production_override", + "target": { + "kind": "all" + }, + "value": 210 + }, + { + "kind": "set_cargo_production_override", + "target": { + "kind": "factory" + }, + "value": 225 + }, + { + "kind": "set_cargo_production_override", + "target": { + "kind": "farm_mine" + }, + "value": 175 + } + ] + } + ] + } +} diff --git a/fixtures/runtime/packed-event-cargo-economics-save-slice.json b/fixtures/runtime/packed-event-cargo-economics-save-slice.json new file mode 100644 index 0000000..825ae3b --- /dev/null +++ b/fixtures/runtime/packed-event-cargo-economics-save-slice.json @@ -0,0 +1,216 @@ +{ + "format_version": 1, + "save_slice_id": "packed-event-cargo-economics-save-slice", + "source": { + "description": "Tracked save-slice document proving the grounded aggregate cargo-economics descriptors execute through bounded cargo override surfaces.", + "original_save_filename": "captured-cargo-economics.gms", + "original_save_sha256": "cargo-economics-sample-sha256", + "notes": [ + "tracked as JSON save-slice document rather than raw .smp", + "pins the grounded aggregate descriptors 105 and 177..179 while named cargo strips remain evidence-blocked" + ] + }, + "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, + "named_locomotive_availability_table": null, + "locomotive_catalog": null, + "cargo_catalog": null, + "company_roster": null, + "chairman_profile_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": 32512, + "records_tag_offset": 32768, + "close_tag_offset": 33280, + "packed_state_version": 1001, + "packed_state_version_hex": "0x000003e9", + "live_id_bound": 64, + "live_record_count": 1, + "live_entry_ids": [64], + "decoded_record_count": 1, + "imported_runtime_record_count": 0, + "records": [ + { + "record_index": 0, + "live_entry_id": 64, + "payload_offset": 32800, + "payload_len": 176, + "decode_status": "executable", + "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": [], + "grouped_effect_row_counts": [4, 0, 0, 0], + "grouped_effect_rows": [ + { + "group_index": 0, + "row_index": 0, + "descriptor_id": 105, + "descriptor_label": "All Cargo Prices", + "target_mask_bits": 8, + "parameter_family": "cargo_price_scalar", + "opcode": 3, + "raw_scalar_value": 180, + "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 All Cargo Prices to 180", + "recovered_cargo_slot": null, + "recovered_cargo_class": null, + "recovered_locomotive_id": null, + "locomotive_name": null, + "notes": [ + "descriptor recovered from checked-in EventEffects semantic catalog" + ] + }, + { + "group_index": 0, + "row_index": 1, + "descriptor_id": 177, + "descriptor_label": "All Cargo Production", + "target_mask_bits": 8, + "parameter_family": "cargo_production_scalar", + "opcode": 3, + "raw_scalar_value": 210, + "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 All Cargo Production to 210", + "recovered_cargo_slot": null, + "recovered_cargo_class": null, + "recovered_locomotive_id": null, + "locomotive_name": null, + "notes": [ + "descriptor recovered from checked-in EventEffects semantic catalog" + ] + }, + { + "group_index": 0, + "row_index": 2, + "descriptor_id": 178, + "descriptor_label": "All Factory Production", + "target_mask_bits": 8, + "parameter_family": "cargo_production_scalar", + "opcode": 3, + "raw_scalar_value": 225, + "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 All Factory Production to 225", + "recovered_cargo_slot": null, + "recovered_cargo_class": "factory", + "recovered_locomotive_id": null, + "locomotive_name": null, + "notes": [ + "descriptor recovered from checked-in EventEffects semantic catalog" + ] + }, + { + "group_index": 0, + "row_index": 3, + "descriptor_id": 179, + "descriptor_label": "All Farm/Mine Production", + "target_mask_bits": 8, + "parameter_family": "cargo_production_scalar", + "opcode": 3, + "raw_scalar_value": 175, + "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 All Farm/Mine Production to 175", + "recovered_cargo_slot": null, + "recovered_cargo_class": "farm_mine", + "recovered_locomotive_id": null, + "locomotive_name": null, + "notes": [ + "descriptor recovered from checked-in EventEffects semantic catalog" + ] + } + ], + "decoded_actions": [ + { + "kind": "set_cargo_price_override", + "target": { + "kind": "all" + }, + "value": 180 + }, + { + "kind": "set_cargo_production_override", + "target": { + "kind": "all" + }, + "value": 210 + }, + { + "kind": "set_cargo_production_override", + "target": { + "kind": "factory" + }, + "value": 225 + }, + { + "kind": "set_cargo_production_override", + "target": { + "kind": "farm_mine" + }, + "value": 175 + } + ], + "executable_import_ready": true, + "notes": [ + "grounded aggregate cargo economics descriptors execute through bounded cargo override surfaces" + ] + } + ] + }, + "notes": [ + "aggregate cargo economics executable sample" + ] + } +} diff --git a/tools/py/build_event_effect_semantic_catalog.py b/tools/py/build_event_effect_semantic_catalog.py index 5a3e448..2a9a442 100644 --- a/tools/py/build_event_effect_semantic_catalog.py +++ b/tools/py/build_event_effect_semantic_catalog.py @@ -55,10 +55,22 @@ def classify(row: dict[str, object]) -> dict[str, object]: runtime_key = normalize_world_scalar_key(label) runtime_status = "executable" executable_in_runtime = True - elif 105 <= descriptor_id <= 176: + elif descriptor_id == 105: parameter_family = "cargo_price_scalar" - elif 177 <= descriptor_id <= 240: + runtime_status = "executable" + executable_in_runtime = True + elif 106 <= descriptor_id <= 176: + parameter_family = "cargo_price_scalar" + elif 177 <= descriptor_id <= 179: parameter_family = "cargo_production_scalar" + runtime_status = "executable" + executable_in_runtime = True + elif 180 <= descriptor_id <= 229: + parameter_family = "cargo_production_scalar" + elif 230 <= descriptor_id <= 240: + parameter_family = "cargo_production_scalar" + runtime_status = "executable" + executable_in_runtime = True elif 241 <= descriptor_id <= 351 or 457 <= descriptor_id <= 474: parameter_family = "locomotive_availability_scalar" elif 352 <= descriptor_id <= 451 or 475 <= descriptor_id <= 502: