Execute descriptor 3 as territory access rights
This commit is contained in:
parent
e481274243
commit
e9c8bfbb9c
20 changed files with 1226 additions and 89 deletions
|
|
@ -24,9 +24,10 @@ company-territory track rows can import through overlay-backed runtime context.
|
||||||
named-territory binding now executes, and the runtime now also carries the minimal event-owned
|
named-territory binding now executes, and the runtime now also carries the minimal event-owned
|
||||||
train roster and opaque economic-status lane needed for real descriptors `8` `Economic Status`, `9`
|
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`
|
`Confiscate All`, and `15` `Retire Train` to execute through the same path. Descriptor `3`
|
||||||
`Territory - Allow All` remains the explicit parity-only descriptor frontier. Mixed
|
`Territory - Allow All` now executes too, reinterpreted as company-to-territory access rights
|
||||||
supported/unsupported real rows still stay parity-only. The PE32 hook remains useful as capture and
|
rather than a territory-owned policy bit. Shell purchase-flow and selected-profile parity remain
|
||||||
integration tooling, but it is no longer the main execution milestone.
|
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
|
## Project Docs
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -179,6 +179,7 @@ mod tests {
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
|
company_territory_access: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
@ -347,6 +348,7 @@ mod tests {
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
|
company_territory_access: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,9 @@ pub struct ExpectedRuntimeSummary {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub packed_event_blocked_unmapped_real_descriptor_count: Option<usize>,
|
pub packed_event_blocked_unmapped_real_descriptor_count: Option<usize>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub packed_event_blocked_territory_policy_descriptor_count: Option<usize>,
|
pub packed_event_blocked_territory_access_variant_count: Option<usize>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub packed_event_blocked_territory_access_scope_count: Option<usize>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub packed_event_blocked_missing_train_context_count: Option<usize>,
|
pub packed_event_blocked_missing_train_context_count: Option<usize>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
|
@ -615,11 +617,19 @@ impl ExpectedRuntimeSummary {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(count) = self.packed_event_blocked_territory_policy_descriptor_count {
|
if let Some(count) = self.packed_event_blocked_territory_access_variant_count {
|
||||||
if actual.packed_event_blocked_territory_policy_descriptor_count != count {
|
if actual.packed_event_blocked_territory_access_variant_count != count {
|
||||||
mismatches.push(format!(
|
mismatches.push(format!(
|
||||||
"packed_event_blocked_territory_policy_descriptor_count mismatch: expected {count}, got {}",
|
"packed_event_blocked_territory_access_variant_count mismatch: expected {count}, got {}",
|
||||||
actual.packed_event_blocked_territory_policy_descriptor_count
|
actual.packed_event_blocked_territory_access_variant_count
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(count) = self.packed_event_blocked_territory_access_scope_count {
|
||||||
|
if actual.packed_event_blocked_territory_access_scope_count != count {
|
||||||
|
mismatches.push(format!(
|
||||||
|
"packed_event_blocked_territory_access_scope_count mismatch: expected {count}, got {}",
|
||||||
|
actual.packed_event_blocked_territory_access_scope_count
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -233,6 +233,7 @@ pub fn project_save_slice_to_runtime_state_import(
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
|
company_territory_access: Vec::new(),
|
||||||
packed_event_collection: projection.packed_event_collection,
|
packed_event_collection: projection.packed_event_collection,
|
||||||
event_runtime_records: projection.event_runtime_records,
|
event_runtime_records: projection.event_runtime_records,
|
||||||
candidate_availability: projection.candidate_availability,
|
candidate_availability: projection.candidate_availability,
|
||||||
|
|
@ -289,6 +290,7 @@ pub fn project_save_slice_overlay_to_runtime_state_import(
|
||||||
company_territory_track_piece_counts: base_state
|
company_territory_track_piece_counts: base_state
|
||||||
.company_territory_track_piece_counts
|
.company_territory_track_piece_counts
|
||||||
.clone(),
|
.clone(),
|
||||||
|
company_territory_access: base_state.company_territory_access.clone(),
|
||||||
packed_event_collection: projection.packed_event_collection,
|
packed_event_collection: projection.packed_event_collection,
|
||||||
event_runtime_records: projection.event_runtime_records,
|
event_runtime_records: projection.event_runtime_records,
|
||||||
candidate_availability: projection.candidate_availability,
|
candidate_availability: projection.candidate_availability,
|
||||||
|
|
@ -1044,6 +1046,18 @@ fn lower_condition_targets_in_effect(
|
||||||
)?,
|
)?,
|
||||||
value: *value,
|
value: *value,
|
||||||
},
|
},
|
||||||
|
RuntimeEffect::SetCompanyTerritoryAccess {
|
||||||
|
target,
|
||||||
|
territory,
|
||||||
|
value,
|
||||||
|
} => RuntimeEffect::SetCompanyTerritoryAccess {
|
||||||
|
target: lower_condition_true_company_target_in_company_target(
|
||||||
|
target,
|
||||||
|
lowered_company_target,
|
||||||
|
)?,
|
||||||
|
territory: territory.clone(),
|
||||||
|
value: *value,
|
||||||
|
},
|
||||||
RuntimeEffect::ConfiscateCompanyAssets { target } => {
|
RuntimeEffect::ConfiscateCompanyAssets { target } => {
|
||||||
RuntimeEffect::ConfiscateCompanyAssets {
|
RuntimeEffect::ConfiscateCompanyAssets {
|
||||||
target: lower_condition_true_company_target_in_company_target(
|
target: lower_condition_true_company_target_in_company_target(
|
||||||
|
|
@ -1332,6 +1346,24 @@ fn smp_runtime_effect_to_runtime_effect(
|
||||||
Err(player_target_import_error_message(target, company_context))
|
Err(player_target_import_error_message(target, company_context))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
RuntimeEffect::SetCompanyTerritoryAccess {
|
||||||
|
target,
|
||||||
|
territory,
|
||||||
|
value,
|
||||||
|
} => {
|
||||||
|
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())
|
||||||
|
} else {
|
||||||
|
Ok(RuntimeEffect::SetCompanyTerritoryAccess {
|
||||||
|
target: target.clone(),
|
||||||
|
territory: territory.clone(),
|
||||||
|
value: *value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
RuntimeEffect::ConfiscateCompanyAssets { target } => {
|
RuntimeEffect::ConfiscateCompanyAssets { target } => {
|
||||||
if company_target_allowed_for_import(
|
if company_target_allowed_for_import(
|
||||||
target,
|
target,
|
||||||
|
|
@ -1724,9 +1756,16 @@ fn determine_packed_event_import_outcome(
|
||||||
if record
|
if record
|
||||||
.grouped_effect_rows
|
.grouped_effect_rows
|
||||||
.iter()
|
.iter()
|
||||||
.any(|row| row.descriptor_id == 3)
|
.any(real_grouped_row_is_unsupported_territory_access_scope)
|
||||||
{
|
{
|
||||||
return "blocked_territory_policy_descriptor".to_string();
|
return "blocked_territory_access_scope".to_string();
|
||||||
|
}
|
||||||
|
if record
|
||||||
|
.grouped_effect_rows
|
||||||
|
.iter()
|
||||||
|
.any(real_grouped_row_is_unsupported_territory_access_variant)
|
||||||
|
{
|
||||||
|
return "blocked_territory_access_variant".to_string();
|
||||||
}
|
}
|
||||||
if record
|
if record
|
||||||
.grouped_effect_rows
|
.grouped_effect_rows
|
||||||
|
|
@ -1867,6 +1906,24 @@ fn territory_ids_match_known_context(ids: &[u32], company_context: &ImportRuntim
|
||||||
.all(|territory_id| company_context.known_territory_ids.contains(territory_id))
|
.all(|territory_id| company_context.known_territory_ids.contains(territory_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn real_grouped_row_is_unsupported_territory_access_variant(
|
||||||
|
row: &SmpLoadedPackedEventGroupedEffectRowSummary,
|
||||||
|
) -> bool {
|
||||||
|
row.descriptor_id == 3 && !(row.row_shape == "bool_toggle" && row.raw_scalar_value != 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn real_grouped_row_is_unsupported_territory_access_scope(
|
||||||
|
row: &SmpLoadedPackedEventGroupedEffectRowSummary,
|
||||||
|
) -> bool {
|
||||||
|
row.descriptor_id == 3
|
||||||
|
&& row.row_shape == "bool_toggle"
|
||||||
|
&& row.raw_scalar_value != 0
|
||||||
|
&& row
|
||||||
|
.notes
|
||||||
|
.iter()
|
||||||
|
.any(|note| note == "territory access row is missing company or territory scope")
|
||||||
|
}
|
||||||
|
|
||||||
fn real_grouped_row_is_unsupported_confiscation_variant(
|
fn real_grouped_row_is_unsupported_confiscation_variant(
|
||||||
row: &SmpLoadedPackedEventGroupedEffectRowSummary,
|
row: &SmpLoadedPackedEventGroupedEffectRowSummary,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
|
|
@ -1894,6 +1951,7 @@ fn real_grouped_row_is_unsupported_retire_train_scope(
|
||||||
fn runtime_effect_uses_condition_true_company(effect: &RuntimeEffect) -> bool {
|
fn runtime_effect_uses_condition_true_company(effect: &RuntimeEffect) -> bool {
|
||||||
match effect {
|
match effect {
|
||||||
RuntimeEffect::SetCompanyCash { target, .. }
|
RuntimeEffect::SetCompanyCash { target, .. }
|
||||||
|
| RuntimeEffect::SetCompanyTerritoryAccess { target, .. }
|
||||||
| RuntimeEffect::ConfiscateCompanyAssets { target }
|
| RuntimeEffect::ConfiscateCompanyAssets { target }
|
||||||
| RuntimeEffect::DeactivateCompany { target }
|
| RuntimeEffect::DeactivateCompany { target }
|
||||||
| RuntimeEffect::SetCompanyTrackLayingCapacity { target, .. }
|
| RuntimeEffect::SetCompanyTrackLayingCapacity { target, .. }
|
||||||
|
|
@ -1939,6 +1997,7 @@ fn runtime_effect_company_target_import_blocker(
|
||||||
) -> Option<ImportBlocker> {
|
) -> Option<ImportBlocker> {
|
||||||
match effect {
|
match effect {
|
||||||
RuntimeEffect::SetCompanyCash { target, .. }
|
RuntimeEffect::SetCompanyCash { target, .. }
|
||||||
|
| RuntimeEffect::SetCompanyTerritoryAccess { target, .. }
|
||||||
| RuntimeEffect::ConfiscateCompanyAssets { target }
|
| RuntimeEffect::ConfiscateCompanyAssets { target }
|
||||||
| RuntimeEffect::DeactivateCompany { target }
|
| RuntimeEffect::DeactivateCompany { target }
|
||||||
| RuntimeEffect::SetCompanyTrackLayingCapacity { target, .. }
|
| RuntimeEffect::SetCompanyTrackLayingCapacity { target, .. }
|
||||||
|
|
@ -1948,6 +2007,9 @@ fn runtime_effect_company_target_import_blocker(
|
||||||
&& !company_context.has_train_context
|
&& !company_context.has_train_context
|
||||||
{
|
{
|
||||||
Some(ImportBlocker::MissingTrainContext)
|
Some(ImportBlocker::MissingTrainContext)
|
||||||
|
} else if let RuntimeEffect::SetCompanyTerritoryAccess { territory, .. } = effect {
|
||||||
|
company_target_import_blocker(target, company_context)
|
||||||
|
.or_else(|| territory_target_import_blocker(territory, company_context))
|
||||||
} else {
|
} else {
|
||||||
company_target_import_blocker(target, company_context)
|
company_target_import_blocker(target, company_context)
|
||||||
}
|
}
|
||||||
|
|
@ -2315,6 +2377,7 @@ mod tests {
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
|
company_territory_access: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
@ -2517,6 +2580,36 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn real_territory_access_row(
|
||||||
|
enabled: bool,
|
||||||
|
notes: Vec<String>,
|
||||||
|
) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||||
|
crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||||
|
group_index: 0,
|
||||||
|
row_index: 0,
|
||||||
|
descriptor_id: 3,
|
||||||
|
descriptor_label: Some("Territory - Allow All".to_string()),
|
||||||
|
target_mask_bits: Some(0x05),
|
||||||
|
parameter_family: Some("territory_access_toggle".to_string()),
|
||||||
|
opcode: 1,
|
||||||
|
raw_scalar_value: if enabled { 1 } else { 0 },
|
||||||
|
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".to_string(),
|
||||||
|
semantic_family: Some("bool_toggle".to_string()),
|
||||||
|
semantic_preview: Some(format!(
|
||||||
|
"Set Territory - Allow All to {}",
|
||||||
|
if enabled { "TRUE" } else { "FALSE" }
|
||||||
|
)),
|
||||||
|
locomotive_name: None,
|
||||||
|
notes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn real_economic_status_row(value: i32) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
fn real_economic_status_row(value: i32) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||||
crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||||
group_index: 0,
|
group_index: 0,
|
||||||
|
|
@ -4139,6 +4232,7 @@ mod tests {
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
|
company_territory_access: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
@ -4265,6 +4359,298 @@ mod tests {
|
||||||
assert_eq!(import.state.companies[0].current_cash, 250);
|
assert_eq!(import.state.companies[0].current_cash, 250);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn overlays_real_territory_access_descriptor_into_executable_runtime_record() {
|
||||||
|
let base_state = RuntimeState {
|
||||||
|
companies: vec![crate::RuntimeCompany {
|
||||||
|
company_id: 42,
|
||||||
|
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||||
|
current_cash: 500,
|
||||||
|
debt: 20,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
|
active: true,
|
||||||
|
available_track_laying_capacity: None,
|
||||||
|
}],
|
||||||
|
selected_company_id: Some(42),
|
||||||
|
territories: vec![crate::RuntimeTerritory {
|
||||||
|
territory_id: 7,
|
||||||
|
name: Some("Appalachia".to_string()),
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
|
}],
|
||||||
|
..state()
|
||||||
|
};
|
||||||
|
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,
|
||||||
|
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: 11,
|
||||||
|
live_record_count: 1,
|
||||||
|
live_entry_ids: vec![11],
|
||||||
|
decoded_record_count: 1,
|
||||||
|
imported_runtime_record_count: 0,
|
||||||
|
records: vec![crate::SmpLoadedPackedEventRecordSummary {
|
||||||
|
record_index: 0,
|
||||||
|
live_entry_id: 11,
|
||||||
|
payload_offset: Some(0x7202),
|
||||||
|
payload_len: Some(120),
|
||||||
|
decode_status: "parity_only".to_string(),
|
||||||
|
payload_family: "real_packed_v1".to_string(),
|
||||||
|
trigger_kind: Some(6),
|
||||||
|
active: None,
|
||||||
|
marks_collection_dirty: None,
|
||||||
|
one_shot: Some(false),
|
||||||
|
compact_control: Some(crate::SmpLoadedPackedEventCompactControlSummary {
|
||||||
|
mode_byte_0x7ef: 6,
|
||||||
|
primary_selector_0x7f0: 12,
|
||||||
|
grouped_mode_0x7f4: 2,
|
||||||
|
one_shot_header_0x7f5: 0,
|
||||||
|
modifier_flag_0x7f9: 0,
|
||||||
|
modifier_flag_0x7fa: 0,
|
||||||
|
grouped_target_scope_ordinals_0x7fb: vec![1, 1, 1, 1],
|
||||||
|
grouped_scope_checkboxes_0x7ff: vec![1, 0, 0, 0],
|
||||||
|
summary_toggle_0x800: 1,
|
||||||
|
grouped_territory_selectors_0x80f: vec![7, -1, -1, -1],
|
||||||
|
}),
|
||||||
|
text_bands: packed_text_bands(),
|
||||||
|
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_territory_access_row(true, vec![])],
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
|
decoded_actions: vec![RuntimeEffect::SetCompanyTerritoryAccess {
|
||||||
|
target: RuntimeCompanyTarget::SelectedCompany,
|
||||||
|
territory: RuntimeTerritoryTarget::Ids { ids: vec![7] },
|
||||||
|
value: true,
|
||||||
|
}],
|
||||||
|
executable_import_ready: true,
|
||||||
|
notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()],
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
notes: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut import = project_save_slice_overlay_to_runtime_state_import(
|
||||||
|
&base_state,
|
||||||
|
&save_slice,
|
||||||
|
"real-territory-access-overlay",
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.expect("overlay import 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: 6 },
|
||||||
|
)
|
||||||
|
.expect("real territory-access descriptor should execute");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
import.state.company_territory_access,
|
||||||
|
vec![crate::RuntimeCompanyTerritoryAccess {
|
||||||
|
company_id: 42,
|
||||||
|
territory_id: 7,
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn keeps_real_territory_access_false_row_parity_only() {
|
||||||
|
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,
|
||||||
|
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: 12,
|
||||||
|
live_record_count: 1,
|
||||||
|
live_entry_ids: vec![12],
|
||||||
|
decoded_record_count: 1,
|
||||||
|
imported_runtime_record_count: 0,
|
||||||
|
records: vec![crate::SmpLoadedPackedEventRecordSummary {
|
||||||
|
record_index: 0,
|
||||||
|
live_entry_id: 12,
|
||||||
|
payload_offset: Some(0x7202),
|
||||||
|
payload_len: Some(120),
|
||||||
|
decode_status: "parity_only".to_string(),
|
||||||
|
payload_family: "real_packed_v1".to_string(),
|
||||||
|
trigger_kind: Some(6),
|
||||||
|
active: None,
|
||||||
|
marks_collection_dirty: None,
|
||||||
|
one_shot: Some(false),
|
||||||
|
compact_control: Some(crate::SmpLoadedPackedEventCompactControlSummary {
|
||||||
|
mode_byte_0x7ef: 6,
|
||||||
|
primary_selector_0x7f0: 12,
|
||||||
|
grouped_mode_0x7f4: 2,
|
||||||
|
one_shot_header_0x7f5: 0,
|
||||||
|
modifier_flag_0x7f9: 0,
|
||||||
|
modifier_flag_0x7fa: 0,
|
||||||
|
grouped_target_scope_ordinals_0x7fb: vec![1, 1, 1, 1],
|
||||||
|
grouped_scope_checkboxes_0x7ff: vec![1, 0, 0, 0],
|
||||||
|
summary_toggle_0x800: 1,
|
||||||
|
grouped_territory_selectors_0x80f: vec![7, -1, -1, -1],
|
||||||
|
}),
|
||||||
|
text_bands: packed_text_bands(),
|
||||||
|
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_territory_access_row(false, vec![])],
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
|
decoded_actions: vec![],
|
||||||
|
executable_import_ready: false,
|
||||||
|
notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()],
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
notes: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let import = project_save_slice_to_runtime_state_import(
|
||||||
|
&save_slice,
|
||||||
|
"real-territory-access-false",
|
||||||
|
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_territory_access_variant")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn keeps_real_territory_access_missing_scope_row_parity_only() {
|
||||||
|
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,
|
||||||
|
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: 13,
|
||||||
|
live_record_count: 1,
|
||||||
|
live_entry_ids: vec![13],
|
||||||
|
decoded_record_count: 1,
|
||||||
|
imported_runtime_record_count: 0,
|
||||||
|
records: vec![crate::SmpLoadedPackedEventRecordSummary {
|
||||||
|
record_index: 0,
|
||||||
|
live_entry_id: 13,
|
||||||
|
payload_offset: Some(0x7202),
|
||||||
|
payload_len: Some(120),
|
||||||
|
decode_status: "parity_only".to_string(),
|
||||||
|
payload_family: "real_packed_v1".to_string(),
|
||||||
|
trigger_kind: Some(6),
|
||||||
|
active: None,
|
||||||
|
marks_collection_dirty: None,
|
||||||
|
one_shot: Some(false),
|
||||||
|
compact_control: Some(crate::SmpLoadedPackedEventCompactControlSummary {
|
||||||
|
mode_byte_0x7ef: 6,
|
||||||
|
primary_selector_0x7f0: 12,
|
||||||
|
grouped_mode_0x7f4: 2,
|
||||||
|
one_shot_header_0x7f5: 0,
|
||||||
|
modifier_flag_0x7f9: 0,
|
||||||
|
modifier_flag_0x7fa: 0,
|
||||||
|
grouped_target_scope_ordinals_0x7fb: vec![9, 1, 1, 1],
|
||||||
|
grouped_scope_checkboxes_0x7ff: vec![1, 0, 0, 0],
|
||||||
|
summary_toggle_0x800: 1,
|
||||||
|
grouped_territory_selectors_0x80f: vec![-1, -1, -1, -1],
|
||||||
|
}),
|
||||||
|
text_bands: packed_text_bands(),
|
||||||
|
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_territory_access_row(
|
||||||
|
true,
|
||||||
|
vec!["territory access row is missing company or territory scope"
|
||||||
|
.to_string()],
|
||||||
|
)],
|
||||||
|
decoded_conditions: Vec::new(),
|
||||||
|
decoded_actions: vec![],
|
||||||
|
executable_import_ready: false,
|
||||||
|
notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()],
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
notes: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let import = project_save_slice_to_runtime_state_import(
|
||||||
|
&save_slice,
|
||||||
|
"real-territory-access-missing-scope",
|
||||||
|
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_territory_access_scope")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn overlays_real_deactivate_company_descriptor_into_executable_runtime_record() {
|
fn overlays_real_deactivate_company_descriptor_into_executable_runtime_record() {
|
||||||
let base_state = RuntimeState {
|
let base_state = RuntimeState {
|
||||||
|
|
@ -5567,6 +5953,7 @@ mod tests {
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
|
company_territory_access: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: vec![RuntimeEventRecord {
|
event_runtime_records: vec![RuntimeEventRecord {
|
||||||
record_id: 1,
|
record_id: 1,
|
||||||
|
|
@ -5742,6 +6129,7 @@ mod tests {
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
|
company_territory_access: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
|
||||||
|
|
@ -36,9 +36,10 @@ pub use pk4::{
|
||||||
};
|
};
|
||||||
pub use runtime::{
|
pub use runtime::{
|
||||||
RuntimeCompany, RuntimeCompanyConditionTestScope, RuntimeCompanyControllerKind,
|
RuntimeCompany, RuntimeCompanyConditionTestScope, RuntimeCompanyControllerKind,
|
||||||
RuntimeCompanyMetric, RuntimeCompanyTarget, RuntimeCompanyTerritoryTrackPieceCount,
|
RuntimeCompanyMetric, RuntimeCompanyTarget, RuntimeCompanyTerritoryAccess,
|
||||||
RuntimeCondition, RuntimeConditionComparator, RuntimeEffect, RuntimeEventRecord,
|
RuntimeCompanyTerritoryTrackPieceCount, RuntimeCondition, RuntimeConditionComparator,
|
||||||
RuntimeEventRecordTemplate, RuntimePackedEventCollectionSummary,
|
RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate,
|
||||||
|
RuntimePackedEventCollectionSummary,
|
||||||
RuntimePackedEventCompactControlSummary, RuntimePackedEventConditionRowSummary,
|
RuntimePackedEventCompactControlSummary, RuntimePackedEventConditionRowSummary,
|
||||||
RuntimePackedEventGroupedEffectRowSummary, RuntimePackedEventNegativeSentinelScopeSummary,
|
RuntimePackedEventGroupedEffectRowSummary, RuntimePackedEventNegativeSentinelScopeSummary,
|
||||||
RuntimePackedEventRecordSummary, RuntimePackedEventTextBandSummary, RuntimePlayer,
|
RuntimePackedEventRecordSummary, RuntimePackedEventTextBandSummary, RuntimePlayer,
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,7 @@ mod tests {
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
|
company_territory_access: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,12 @@ pub struct RuntimeCompanyTerritoryTrackPieceCount {
|
||||||
pub track_piece_counts: RuntimeTrackPieceCounts,
|
pub track_piece_counts: RuntimeTrackPieceCounts,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct RuntimeCompanyTerritoryAccess {
|
||||||
|
pub company_id: u32,
|
||||||
|
pub territory_id: u32,
|
||||||
|
}
|
||||||
|
|
||||||
fn runtime_player_default_active() -> bool {
|
fn runtime_player_default_active() -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
@ -242,6 +248,11 @@ pub enum RuntimeEffect {
|
||||||
target: RuntimePlayerTarget,
|
target: RuntimePlayerTarget,
|
||||||
value: i64,
|
value: i64,
|
||||||
},
|
},
|
||||||
|
SetCompanyTerritoryAccess {
|
||||||
|
target: RuntimeCompanyTarget,
|
||||||
|
territory: RuntimeTerritoryTarget,
|
||||||
|
value: bool,
|
||||||
|
},
|
||||||
ConfiscateCompanyAssets {
|
ConfiscateCompanyAssets {
|
||||||
target: RuntimeCompanyTarget,
|
target: RuntimeCompanyTarget,
|
||||||
},
|
},
|
||||||
|
|
@ -592,6 +603,8 @@ pub struct RuntimeState {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub company_territory_track_piece_counts: Vec<RuntimeCompanyTerritoryTrackPieceCount>,
|
pub company_territory_track_piece_counts: Vec<RuntimeCompanyTerritoryTrackPieceCount>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub company_territory_access: Vec<RuntimeCompanyTerritoryAccess>,
|
||||||
|
#[serde(default)]
|
||||||
pub packed_event_collection: Option<RuntimePackedEventCollectionSummary>,
|
pub packed_event_collection: Option<RuntimePackedEventCollectionSummary>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub event_runtime_records: Vec<RuntimeEventRecord>,
|
pub event_runtime_records: Vec<RuntimeEventRecord>,
|
||||||
|
|
@ -725,6 +738,27 @@ impl RuntimeState {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let mut seen_company_territory_access = BTreeSet::new();
|
||||||
|
for entry in &self.company_territory_access {
|
||||||
|
if !seen_company_ids.contains(&entry.company_id) {
|
||||||
|
return Err(format!(
|
||||||
|
"company_territory_access references unknown company_id {}",
|
||||||
|
entry.company_id
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if !seen_territory_ids.contains(&entry.territory_id) {
|
||||||
|
return Err(format!(
|
||||||
|
"company_territory_access references unknown territory_id {}",
|
||||||
|
entry.territory_id
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if !seen_company_territory_access.insert((entry.company_id, entry.territory_id)) {
|
||||||
|
return Err(format!(
|
||||||
|
"duplicate company_territory_access pair ({}, {})",
|
||||||
|
entry.company_id, entry.territory_id
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut seen_record_ids = BTreeSet::new();
|
let mut seen_record_ids = BTreeSet::new();
|
||||||
for record in &self.event_runtime_records {
|
for record in &self.event_runtime_records {
|
||||||
|
|
@ -1090,6 +1124,12 @@ fn validate_runtime_effect(
|
||||||
| RuntimeEffect::AdjustCompanyDebt { target, .. } => {
|
| RuntimeEffect::AdjustCompanyDebt { target, .. } => {
|
||||||
validate_company_target(target, valid_company_ids)?;
|
validate_company_target(target, valid_company_ids)?;
|
||||||
}
|
}
|
||||||
|
RuntimeEffect::SetCompanyTerritoryAccess {
|
||||||
|
target, territory, ..
|
||||||
|
} => {
|
||||||
|
validate_company_target(target, valid_company_ids)?;
|
||||||
|
validate_territory_target(territory, valid_territory_ids)?;
|
||||||
|
}
|
||||||
RuntimeEffect::SetPlayerCash { target, .. } => {
|
RuntimeEffect::SetPlayerCash { target, .. } => {
|
||||||
validate_player_target(target, valid_player_ids)?;
|
validate_player_target(target, valid_player_ids)?;
|
||||||
}
|
}
|
||||||
|
|
@ -1315,6 +1355,7 @@ mod tests {
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
|
company_territory_access: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
@ -1368,6 +1409,7 @@ mod tests {
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
|
company_territory_access: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
@ -1408,6 +1450,7 @@ mod tests {
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
|
company_territory_access: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: vec![RuntimeEventRecord {
|
event_runtime_records: vec![RuntimeEventRecord {
|
||||||
record_id: 7,
|
record_id: 7,
|
||||||
|
|
@ -1461,6 +1504,7 @@ mod tests {
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
|
company_territory_access: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: vec![RuntimeEventRecord {
|
event_runtime_records: vec![RuntimeEventRecord {
|
||||||
record_id: 7,
|
record_id: 7,
|
||||||
|
|
@ -1514,6 +1558,7 @@ mod tests {
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
|
company_territory_access: Vec::new(),
|
||||||
packed_event_collection: Some(RuntimePackedEventCollectionSummary {
|
packed_event_collection: Some(RuntimePackedEventCollectionSummary {
|
||||||
source_kind: "packed-event-runtime-collection".to_string(),
|
source_kind: "packed-event-runtime-collection".to_string(),
|
||||||
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
||||||
|
|
@ -1618,6 +1663,7 @@ mod tests {
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
|
company_territory_access: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
@ -1658,6 +1704,7 @@ mod tests {
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
|
company_territory_access: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
@ -1715,6 +1762,7 @@ mod tests {
|
||||||
],
|
],
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
|
company_territory_access: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
@ -1762,6 +1810,7 @@ mod tests {
|
||||||
}],
|
}],
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
|
company_territory_access: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
@ -1813,6 +1862,7 @@ mod tests {
|
||||||
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
}],
|
}],
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
|
company_territory_access: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
@ -1860,6 +1910,157 @@ mod tests {
|
||||||
}],
|
}],
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
|
company_territory_access: Vec::new(),
|
||||||
|
packed_event_collection: None,
|
||||||
|
event_runtime_records: Vec::new(),
|
||||||
|
candidate_availability: BTreeMap::new(),
|
||||||
|
special_conditions: BTreeMap::new(),
|
||||||
|
service_state: RuntimeServiceState::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(state.validate().is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_duplicate_company_territory_access_pairs() {
|
||||||
|
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![RuntimeCompany {
|
||||||
|
company_id: 1,
|
||||||
|
current_cash: 100,
|
||||||
|
debt: 0,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
|
active: true,
|
||||||
|
available_track_laying_capacity: None,
|
||||||
|
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||||
|
}],
|
||||||
|
selected_company_id: None,
|
||||||
|
players: Vec::new(),
|
||||||
|
selected_player_id: None,
|
||||||
|
trains: Vec::new(),
|
||||||
|
territories: vec![RuntimeTerritory {
|
||||||
|
territory_id: 7,
|
||||||
|
name: Some("Appalachia".to_string()),
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
|
}],
|
||||||
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
|
company_territory_access: vec![
|
||||||
|
RuntimeCompanyTerritoryAccess {
|
||||||
|
company_id: 1,
|
||||||
|
territory_id: 7,
|
||||||
|
},
|
||||||
|
RuntimeCompanyTerritoryAccess {
|
||||||
|
company_id: 1,
|
||||||
|
territory_id: 7,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
packed_event_collection: None,
|
||||||
|
event_runtime_records: Vec::new(),
|
||||||
|
candidate_availability: BTreeMap::new(),
|
||||||
|
special_conditions: BTreeMap::new(),
|
||||||
|
service_state: RuntimeServiceState::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(state.validate().is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_company_territory_access_with_unknown_company() {
|
||||||
|
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![RuntimeCompany {
|
||||||
|
company_id: 1,
|
||||||
|
current_cash: 100,
|
||||||
|
debt: 0,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
|
active: true,
|
||||||
|
available_track_laying_capacity: None,
|
||||||
|
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||||
|
}],
|
||||||
|
selected_company_id: None,
|
||||||
|
players: Vec::new(),
|
||||||
|
selected_player_id: None,
|
||||||
|
trains: Vec::new(),
|
||||||
|
territories: vec![RuntimeTerritory {
|
||||||
|
territory_id: 7,
|
||||||
|
name: Some("Appalachia".to_string()),
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
|
}],
|
||||||
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
|
company_territory_access: vec![RuntimeCompanyTerritoryAccess {
|
||||||
|
company_id: 2,
|
||||||
|
territory_id: 7,
|
||||||
|
}],
|
||||||
|
packed_event_collection: None,
|
||||||
|
event_runtime_records: Vec::new(),
|
||||||
|
candidate_availability: BTreeMap::new(),
|
||||||
|
special_conditions: BTreeMap::new(),
|
||||||
|
service_state: RuntimeServiceState::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(state.validate().is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_company_territory_access_with_unknown_territory() {
|
||||||
|
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![RuntimeCompany {
|
||||||
|
company_id: 1,
|
||||||
|
current_cash: 100,
|
||||||
|
debt: 0,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
|
active: true,
|
||||||
|
available_track_laying_capacity: None,
|
||||||
|
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||||
|
}],
|
||||||
|
selected_company_id: None,
|
||||||
|
players: Vec::new(),
|
||||||
|
selected_player_id: None,
|
||||||
|
trains: Vec::new(),
|
||||||
|
territories: vec![RuntimeTerritory {
|
||||||
|
territory_id: 7,
|
||||||
|
name: Some("Appalachia".to_string()),
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
|
}],
|
||||||
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
|
company_territory_access: vec![RuntimeCompanyTerritoryAccess {
|
||||||
|
company_id: 1,
|
||||||
|
territory_id: 8,
|
||||||
|
}],
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
|
||||||
|
|
@ -147,7 +147,7 @@ const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetad
|
||||||
label: "Territory - Allow All",
|
label: "Territory - Allow All",
|
||||||
target_mask_bits: 0x05,
|
target_mask_bits: 0x05,
|
||||||
parameter_family: "territory_access_toggle",
|
parameter_family: "territory_access_toggle",
|
||||||
executable_in_runtime: false,
|
executable_in_runtime: true,
|
||||||
},
|
},
|
||||||
RealGroupedEffectDescriptorMetadata {
|
RealGroupedEffectDescriptorMetadata {
|
||||||
descriptor_id: 8,
|
descriptor_id: 8,
|
||||||
|
|
@ -2102,12 +2102,6 @@ fn parse_real_event_runtime_record_summary(
|
||||||
}
|
}
|
||||||
if let Some(control) = compact_control.as_ref() {
|
if let Some(control) = compact_control.as_ref() {
|
||||||
for row in &mut grouped_effect_rows {
|
for row in &mut grouped_effect_rows {
|
||||||
if row.descriptor_id != 15
|
|
||||||
|| row.row_shape != "bool_toggle"
|
|
||||||
|| row.raw_scalar_value == 0
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let company_target_present = control
|
let company_target_present = control
|
||||||
.grouped_target_scope_ordinals_0x7fb
|
.grouped_target_scope_ordinals_0x7fb
|
||||||
.get(row.group_index)
|
.get(row.group_index)
|
||||||
|
|
@ -2118,10 +2112,24 @@ fn parse_real_event_runtime_record_summary(
|
||||||
.grouped_territory_selectors_0x80f
|
.grouped_territory_selectors_0x80f
|
||||||
.get(row.group_index)
|
.get(row.group_index)
|
||||||
.is_some_and(|selector| *selector >= 0);
|
.is_some_and(|selector| *selector >= 0);
|
||||||
if !company_target_present && !territory_target_present {
|
if row.descriptor_id == 15
|
||||||
|
&& row.row_shape == "bool_toggle"
|
||||||
|
&& row.raw_scalar_value != 0
|
||||||
|
&& !company_target_present
|
||||||
|
&& !territory_target_present
|
||||||
|
{
|
||||||
row.notes
|
row.notes
|
||||||
.push("retire train row is missing company and territory scope".to_string());
|
.push("retire train row is missing company and territory scope".to_string());
|
||||||
}
|
}
|
||||||
|
if row.descriptor_id == 3
|
||||||
|
&& row.row_shape == "bool_toggle"
|
||||||
|
&& 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(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2643,6 +2651,27 @@ fn decode_real_grouped_effect_action(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if descriptor_metadata.executable_in_runtime
|
||||||
|
&& descriptor_metadata.descriptor_id == 3
|
||||||
|
&& row.row_shape == "bool_toggle"
|
||||||
|
&& row.raw_scalar_value != 0
|
||||||
|
{
|
||||||
|
let target = real_grouped_company_target(target_scope_ordinal)?;
|
||||||
|
let territory = compact_control
|
||||||
|
.grouped_territory_selectors_0x80f
|
||||||
|
.get(row.group_index)
|
||||||
|
.copied()
|
||||||
|
.filter(|selector| *selector >= 0)
|
||||||
|
.map(|selector| RuntimeTerritoryTarget::Ids {
|
||||||
|
ids: vec![selector as u32],
|
||||||
|
})?;
|
||||||
|
return Some(RuntimeEffect::SetCompanyTerritoryAccess {
|
||||||
|
target,
|
||||||
|
territory,
|
||||||
|
value: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if descriptor_metadata.executable_in_runtime
|
if descriptor_metadata.executable_in_runtime
|
||||||
&& descriptor_metadata.descriptor_id == 8
|
&& descriptor_metadata.descriptor_id == 8
|
||||||
&& row.row_shape == "scalar_assignment"
|
&& row.row_shape == "scalar_assignment"
|
||||||
|
|
@ -2896,6 +2925,20 @@ fn runtime_effect_supported_for_save_import(effect: &RuntimeEffect) -> bool {
|
||||||
| RuntimePlayerTarget::SelectedPlayer
|
| RuntimePlayerTarget::SelectedPlayer
|
||||||
| RuntimePlayerTarget::ConditionTruePlayer
|
| RuntimePlayerTarget::ConditionTruePlayer
|
||||||
),
|
),
|
||||||
|
RuntimeEffect::SetCompanyTerritoryAccess {
|
||||||
|
target, territory, ..
|
||||||
|
} => matches!(
|
||||||
|
target,
|
||||||
|
RuntimeCompanyTarget::AllActive
|
||||||
|
| RuntimeCompanyTarget::Ids { .. }
|
||||||
|
| RuntimeCompanyTarget::HumanCompanies
|
||||||
|
| RuntimeCompanyTarget::AiCompanies
|
||||||
|
| RuntimeCompanyTarget::SelectedCompany
|
||||||
|
| RuntimeCompanyTarget::ConditionTrueCompany
|
||||||
|
) && matches!(
|
||||||
|
territory,
|
||||||
|
RuntimeTerritoryTarget::AllTerritories | RuntimeTerritoryTarget::Ids { .. }
|
||||||
|
),
|
||||||
RuntimeEffect::SetCompanyCash { target, .. }
|
RuntimeEffect::SetCompanyCash { target, .. }
|
||||||
| RuntimeEffect::AdjustCompanyCash { target, .. }
|
| RuntimeEffect::AdjustCompanyCash { target, .. }
|
||||||
| RuntimeEffect::AdjustCompanyDebt { target, .. } => matches!(
|
| RuntimeEffect::AdjustCompanyDebt { target, .. } => matches!(
|
||||||
|
|
|
||||||
|
|
@ -342,6 +342,21 @@ fn apply_runtime_effects(
|
||||||
mutated_player_ids.insert(player_id);
|
mutated_player_ids.insert(player_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
RuntimeEffect::SetCompanyTerritoryAccess {
|
||||||
|
target,
|
||||||
|
territory,
|
||||||
|
value,
|
||||||
|
} => {
|
||||||
|
let company_ids = resolve_company_target_ids(state, target, condition_context)?;
|
||||||
|
let territory_ids = resolve_territory_target_ids(state, territory)?;
|
||||||
|
set_company_territory_access_pairs(
|
||||||
|
&mut state.company_territory_access,
|
||||||
|
&company_ids,
|
||||||
|
&territory_ids,
|
||||||
|
*value,
|
||||||
|
);
|
||||||
|
mutated_company_ids.extend(company_ids);
|
||||||
|
}
|
||||||
RuntimeEffect::ConfiscateCompanyAssets { target } => {
|
RuntimeEffect::ConfiscateCompanyAssets { target } => {
|
||||||
let company_ids = resolve_company_target_ids(state, target, condition_context)?;
|
let company_ids = resolve_company_target_ids(state, target, condition_context)?;
|
||||||
for company_id in company_ids.iter().copied() {
|
for company_id in company_ids.iter().copied() {
|
||||||
|
|
@ -1003,6 +1018,32 @@ fn retire_matching_trains(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_company_territory_access_pairs(
|
||||||
|
access_entries: &mut Vec<crate::RuntimeCompanyTerritoryAccess>,
|
||||||
|
company_ids: &[u32],
|
||||||
|
territory_ids: &[u32],
|
||||||
|
value: bool,
|
||||||
|
) {
|
||||||
|
if value {
|
||||||
|
for company_id in company_ids {
|
||||||
|
for territory_id in territory_ids {
|
||||||
|
if !access_entries.iter().any(|entry| {
|
||||||
|
entry.company_id == *company_id && entry.territory_id == *territory_id
|
||||||
|
}) {
|
||||||
|
access_entries.push(crate::RuntimeCompanyTerritoryAccess {
|
||||||
|
company_id: *company_id,
|
||||||
|
territory_id: *territory_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
access_entries.retain(|entry| {
|
||||||
|
!(company_ids.contains(&entry.company_id) && territory_ids.contains(&entry.territory_id))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
@ -1044,6 +1085,7 @@ mod tests {
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
|
company_territory_access: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
@ -1603,6 +1645,117 @@ mod tests {
|
||||||
assert_eq!(result.service_events[0].mutated_company_ids, vec![2]);
|
assert_eq!(result.service_events[0].mutated_company_ids, vec![2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sets_and_clears_company_territory_access_for_resolved_targets() {
|
||||||
|
let mut state = RuntimeState {
|
||||||
|
companies: vec![
|
||||||
|
RuntimeCompany {
|
||||||
|
company_id: 1,
|
||||||
|
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||||
|
current_cash: 10,
|
||||||
|
debt: 0,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
|
active: true,
|
||||||
|
available_track_laying_capacity: None,
|
||||||
|
},
|
||||||
|
RuntimeCompany {
|
||||||
|
company_id: 2,
|
||||||
|
controller_kind: RuntimeCompanyControllerKind::Ai,
|
||||||
|
current_cash: 20,
|
||||||
|
debt: 0,
|
||||||
|
credit_rating_score: None,
|
||||||
|
prime_rate: None,
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
|
active: true,
|
||||||
|
available_track_laying_capacity: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
territories: vec![
|
||||||
|
RuntimeTerritory {
|
||||||
|
territory_id: 7,
|
||||||
|
name: Some("Appalachia".to_string()),
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
|
},
|
||||||
|
RuntimeTerritory {
|
||||||
|
territory_id: 8,
|
||||||
|
name: Some("Great Plains".to_string()),
|
||||||
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
event_runtime_records: vec![
|
||||||
|
RuntimeEventRecord {
|
||||||
|
record_id: 21,
|
||||||
|
trigger_kind: 7,
|
||||||
|
active: true,
|
||||||
|
service_count: 0,
|
||||||
|
marks_collection_dirty: false,
|
||||||
|
one_shot: true,
|
||||||
|
has_fired: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
|
effects: vec![RuntimeEffect::SetCompanyTerritoryAccess {
|
||||||
|
target: RuntimeCompanyTarget::SelectedCompany,
|
||||||
|
territory: RuntimeTerritoryTarget::Ids { ids: vec![7, 8] },
|
||||||
|
value: true,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
RuntimeEventRecord {
|
||||||
|
record_id: 22,
|
||||||
|
trigger_kind: 8,
|
||||||
|
active: true,
|
||||||
|
service_count: 0,
|
||||||
|
marks_collection_dirty: false,
|
||||||
|
one_shot: true,
|
||||||
|
has_fired: false,
|
||||||
|
conditions: Vec::new(),
|
||||||
|
effects: vec![RuntimeEffect::SetCompanyTerritoryAccess {
|
||||||
|
target: RuntimeCompanyTarget::SelectedCompany,
|
||||||
|
territory: RuntimeTerritoryTarget::Ids { ids: vec![8] },
|
||||||
|
value: false,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
selected_company_id: Some(1),
|
||||||
|
..state()
|
||||||
|
};
|
||||||
|
|
||||||
|
let first = execute_step_command(
|
||||||
|
&mut state,
|
||||||
|
&StepCommand::ServiceTriggerKind { trigger_kind: 7 },
|
||||||
|
)
|
||||||
|
.expect("territory access grant should succeed");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
state.company_territory_access,
|
||||||
|
vec![
|
||||||
|
crate::RuntimeCompanyTerritoryAccess {
|
||||||
|
company_id: 1,
|
||||||
|
territory_id: 7,
|
||||||
|
},
|
||||||
|
crate::RuntimeCompanyTerritoryAccess {
|
||||||
|
company_id: 1,
|
||||||
|
territory_id: 8,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert_eq!(first.service_events[0].mutated_company_ids, vec![1]);
|
||||||
|
|
||||||
|
execute_step_command(
|
||||||
|
&mut state,
|
||||||
|
&StepCommand::ServiceTriggerKind { trigger_kind: 8 },
|
||||||
|
)
|
||||||
|
.expect("territory access clear should succeed");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
state.company_territory_access,
|
||||||
|
vec![crate::RuntimeCompanyTerritoryAccess {
|
||||||
|
company_id: 1,
|
||||||
|
territory_id: 7,
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rejects_condition_true_company_target_without_condition_context() {
|
fn rejects_condition_true_company_target_without_condition_context() {
|
||||||
let mut state = RuntimeState {
|
let mut state = RuntimeState {
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,8 @@ pub struct RuntimeSummary {
|
||||||
pub packed_event_blocked_unmapped_ordinary_condition_count: usize,
|
pub packed_event_blocked_unmapped_ordinary_condition_count: usize,
|
||||||
pub packed_event_blocked_missing_compact_control_count: usize,
|
pub packed_event_blocked_missing_compact_control_count: usize,
|
||||||
pub packed_event_blocked_unmapped_real_descriptor_count: usize,
|
pub packed_event_blocked_unmapped_real_descriptor_count: usize,
|
||||||
pub packed_event_blocked_territory_policy_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,
|
pub packed_event_blocked_missing_train_context_count: usize,
|
||||||
pub packed_event_blocked_missing_train_territory_context_count: usize,
|
pub packed_event_blocked_missing_train_territory_context_count: usize,
|
||||||
pub packed_event_blocked_confiscation_variant_count: usize,
|
pub packed_event_blocked_confiscation_variant_count: usize,
|
||||||
|
|
@ -420,7 +421,7 @@ impl RuntimeSummary {
|
||||||
.count()
|
.count()
|
||||||
})
|
})
|
||||||
.unwrap_or(0),
|
.unwrap_or(0),
|
||||||
packed_event_blocked_territory_policy_descriptor_count: state
|
packed_event_blocked_territory_access_variant_count: state
|
||||||
.packed_event_collection
|
.packed_event_collection
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|summary| {
|
.map(|summary| {
|
||||||
|
|
@ -429,7 +430,21 @@ impl RuntimeSummary {
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|record| {
|
.filter(|record| {
|
||||||
record.import_outcome.as_deref()
|
record.import_outcome.as_deref()
|
||||||
== Some("blocked_territory_policy_descriptor")
|
== Some("blocked_territory_access_variant")
|
||||||
|
})
|
||||||
|
.count()
|
||||||
|
})
|
||||||
|
.unwrap_or(0),
|
||||||
|
packed_event_blocked_territory_access_scope_count: state
|
||||||
|
.packed_event_collection
|
||||||
|
.as_ref()
|
||||||
|
.map(|summary| {
|
||||||
|
summary
|
||||||
|
.records
|
||||||
|
.iter()
|
||||||
|
.filter(|record| {
|
||||||
|
record.import_outcome.as_deref()
|
||||||
|
== Some("blocked_territory_access_scope")
|
||||||
})
|
})
|
||||||
.count()
|
.count()
|
||||||
})
|
})
|
||||||
|
|
@ -587,6 +602,7 @@ mod tests {
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
|
company_territory_access: Vec::new(),
|
||||||
packed_event_collection: Some(RuntimePackedEventCollectionSummary {
|
packed_event_collection: Some(RuntimePackedEventCollectionSummary {
|
||||||
source_kind: "packed-event-runtime-collection".to_string(),
|
source_kind: "packed-event-runtime-collection".to_string(),
|
||||||
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
||||||
|
|
@ -815,6 +831,7 @@ mod tests {
|
||||||
trains: Vec::new(),
|
trains: Vec::new(),
|
||||||
territories: Vec::new(),
|
territories: Vec::new(),
|
||||||
company_territory_track_piece_counts: Vec::new(),
|
company_territory_track_piece_counts: Vec::new(),
|
||||||
|
company_territory_access: Vec::new(),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
|
||||||
|
|
@ -94,8 +94,9 @@ The highest-value next passes are now:
|
||||||
- real descriptors `8` `Economic Status`, `9` `Confiscate All`, and `15` `Retire Train` now join
|
- real descriptors `8` `Economic Status`, `9` `Confiscate All`, and `15` `Retire Train` now join
|
||||||
the executable batch through the same ordinary runtime path, backed by the opaque economic-status
|
the executable batch through the same ordinary runtime path, backed by the opaque economic-status
|
||||||
lane and the minimal event-owned train roster
|
lane and the minimal event-owned train roster
|
||||||
- descriptor `3` `Territory - Allow All` remains the explicit parity-only descriptor frontier, and
|
- descriptor `3` `Territory - Allow All` now executes as company-to-territory access rights through
|
||||||
mixed supported/unsupported real rows still stay parity-only
|
the same ordinary runtime path; shell purchase-flow parity remains out of scope, and mixed
|
||||||
|
supported/unsupported real rows still stay parity-only
|
||||||
- keep in mind that the current local `.gms` corpus still exports with no packed event collection,
|
- keep in mind that the current local `.gms` corpus still exports with no packed event collection,
|
||||||
so real descriptor mapping needs to stay plumbing-first until better captures exist
|
so real descriptor mapping needs to stay plumbing-first until better captures exist
|
||||||
- use `rrt-hook` primarily as optional capture or integration tooling, not as the first execution
|
- use `rrt-hook` primarily as optional capture or integration tooling, not as the first execution
|
||||||
|
|
|
||||||
|
|
@ -44,13 +44,14 @@ Implemented today:
|
||||||
state, and real descriptors `8` = `Economic Status`, `9` = `Confiscate All`, and `15` =
|
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
|
`Retire Train` now import and execute through the ordinary runtime path when overlay context
|
||||||
supplies the required train ownership data
|
supplies the required train ownership data
|
||||||
- descriptor `3` = `Territory - Allow All` remains the explicit parity-only descriptor frontier
|
- descriptor `3` = `Territory - Allow All` now imports and executes too, reinterpreted as
|
||||||
instead of hiding behind the generic unmapped bucket
|
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
|
||||||
|
|
||||||
That means the next implementation work is breadth, not bootstrap. The recommended next slice is
|
That means the next implementation work is breadth, not bootstrap. The recommended next slice is
|
||||||
broader real policy-descriptor coverage beyond `3/8/9/15`, wider ordinary condition-id coverage
|
broader real grouped-descriptor and ordinary condition-id coverage beyond the current access,
|
||||||
beyond the current numeric-threshold batch, and richer train/runtime simulation only if later
|
world, train, player, and numeric-threshold batches, plus richer runtime ownership only where a
|
||||||
descriptor families need more than the current event-owned roster.
|
later descriptor family needs more than the current event-owned roster.
|
||||||
|
|
||||||
## Why This Boundary
|
## Why This Boundary
|
||||||
|
|
||||||
|
|
@ -394,8 +395,9 @@ Checked-in fixture families already include:
|
||||||
|
|
||||||
## Next Slice
|
## Next Slice
|
||||||
|
|
||||||
The recommended next implementation slice is broader ordinary-condition breadth on top of the
|
The recommended next implementation slice is broader ordinary-condition and grouped-descriptor
|
||||||
now-stable numeric-threshold, overlay-context, and current company-scoped real-descriptor batch.
|
breadth on top of the now-stable numeric-threshold, overlay-context, named-territory, player,
|
||||||
|
world/train, and company-territory-access batches.
|
||||||
|
|
||||||
Target behavior:
|
Target behavior:
|
||||||
|
|
||||||
|
|
@ -412,6 +414,8 @@ Target behavior:
|
||||||
richer player metrics or profile/chairman ownership
|
richer player metrics or profile/chairman ownership
|
||||||
- continue widening real grouped-descriptor execution only when both descriptor identity and
|
- continue widening real grouped-descriptor execution only when both descriptor identity and
|
||||||
runtime effect semantics are grounded enough to map into the normalized runtime path honestly
|
runtime effect semantics are grounded enough to map into the normalized runtime path honestly
|
||||||
|
- keep descriptor `3` on the now-executable company-territory-access interpretation; do not drift
|
||||||
|
back into territory-owned policy wording without new contrary evidence
|
||||||
|
|
||||||
Public-model expectations for that slice:
|
Public-model expectations for that slice:
|
||||||
|
|
||||||
|
|
@ -427,8 +431,8 @@ Fixture work for that slice:
|
||||||
- preserve the new ordinary-condition tracked overlays for executable company finance, company
|
- preserve the new ordinary-condition tracked overlays for executable company finance, company
|
||||||
track, aggregate territory track, and company-territory track thresholds
|
track, aggregate territory track, and company-territory track thresholds
|
||||||
- preserve the named-territory no-match tracked overlay as the explicit binding blocker frontier
|
- preserve the named-territory no-match tracked overlay as the explicit binding blocker frontier
|
||||||
- preserve the territory-policy tracked sample as the explicit descriptor frontier until mutation
|
- preserve the territory-access tracked overlays and parity samples so descriptor `3` access-rights
|
||||||
semantics are grounded strongly enough to move beyond parity-only
|
execution does not regress while other grouped families widen
|
||||||
- keep the older negative-sentinel, mixed real-row, and company-scoped descriptor fixtures green so
|
- keep the older negative-sentinel, mixed real-row, and company-scoped descriptor fixtures green so
|
||||||
ordinary-condition breadth does not regress descriptor-side execution
|
ordinary-condition breadth does not regress descriptor-side execution
|
||||||
- keep synthetic harness, save-slice, and overlay paths green as the real descriptor surface widens
|
- keep synthetic harness, save-slice, and overlay paths green as the real descriptor surface widens
|
||||||
|
|
@ -442,6 +446,6 @@ Current local constraint:
|
||||||
Do not mix this slice with:
|
Do not mix this slice with:
|
||||||
|
|
||||||
- shell queue/modal behavior
|
- shell queue/modal behavior
|
||||||
- territory-access or selected-profile parity
|
- shell territory-access purchase or selected-profile parity
|
||||||
- speculative condition execution without grounded runtime ownership
|
- speculative condition execution without grounded runtime ownership
|
||||||
- speculative executable import for real rows whose descriptor meaning is still weak
|
- speculative executable import for real rows whose descriptor meaning is still weak
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"fixture_id": "packed-event-territory-access-false-save-slice-fixture",
|
||||||
|
"source": {
|
||||||
|
"kind": "captured-runtime",
|
||||||
|
"description": "Fixture keeping the unsupported FALSE Territory - Allow All variant explicit."
|
||||||
|
},
|
||||||
|
"state_save_slice_path": "packed-event-territory-access-false-save-slice.json",
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"kind": "service_trigger_kind",
|
||||||
|
"trigger_kind": 6
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"expected_summary": {
|
||||||
|
"packed_event_collection_present": true,
|
||||||
|
"packed_event_record_count": 1,
|
||||||
|
"packed_event_decoded_record_count": 1,
|
||||||
|
"packed_event_imported_runtime_record_count": 0,
|
||||||
|
"event_runtime_record_count": 0,
|
||||||
|
"packed_event_blocked_territory_access_variant_count": 1
|
||||||
|
},
|
||||||
|
"expected_state_fragment": {
|
||||||
|
"packed_event_collection": {
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"import_outcome": "blocked_territory_access_variant"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
{
|
{
|
||||||
"format_version": 1,
|
"format_version": 1,
|
||||||
"save_slice_id": "packed-event-territory-policy-save-slice",
|
"save_slice_id": "packed-event-territory-access-false-save-slice",
|
||||||
"source": {
|
"source": {
|
||||||
"description": "Tracked save-slice document with a real Territory - Allow All row that stays parity-only.",
|
"description": "Tracked save-slice document with an unsupported FALSE Territory - Allow All row.",
|
||||||
"original_save_filename": "captured-territory-policy.gms",
|
"original_save_filename": "captured-territory-access-false.gms",
|
||||||
"original_save_sha256": "territory-policy-sample-sha256",
|
"original_save_sha256": "territory-access-false-sample-sha256",
|
||||||
"notes": [
|
"notes": [
|
||||||
"tracked as JSON save-slice document rather than raw .smp",
|
"tracked as JSON save-slice document rather than raw .smp",
|
||||||
"keeps descriptor 3 explicit without guessing territory policy mutation semantics"
|
"keeps the unsupported descriptor 3 FALSE variant explicit"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"save_slice": {
|
"save_slice": {
|
||||||
|
|
@ -30,16 +30,16 @@
|
||||||
"close_tag_offset": 29696,
|
"close_tag_offset": 29696,
|
||||||
"packed_state_version": 1001,
|
"packed_state_version": 1001,
|
||||||
"packed_state_version_hex": "0x000003e9",
|
"packed_state_version_hex": "0x000003e9",
|
||||||
"live_id_bound": 48,
|
"live_id_bound": 49,
|
||||||
"live_record_count": 1,
|
"live_record_count": 1,
|
||||||
"live_entry_ids": [48],
|
"live_entry_ids": [49],
|
||||||
"decoded_record_count": 1,
|
"decoded_record_count": 1,
|
||||||
"imported_runtime_record_count": 0,
|
"imported_runtime_record_count": 0,
|
||||||
"records": [
|
"records": [
|
||||||
{
|
{
|
||||||
"record_index": 0,
|
"record_index": 0,
|
||||||
"live_entry_id": 48,
|
"live_entry_id": 49,
|
||||||
"payload_offset": 29320,
|
"payload_offset": 29360,
|
||||||
"payload_len": 132,
|
"payload_len": 132,
|
||||||
"decode_status": "parity_only",
|
"decode_status": "parity_only",
|
||||||
"payload_family": "real_packed_v1",
|
"payload_family": "real_packed_v1",
|
||||||
|
|
@ -71,7 +71,7 @@
|
||||||
"target_mask_bits": 5,
|
"target_mask_bits": 5,
|
||||||
"parameter_family": "territory_access_toggle",
|
"parameter_family": "territory_access_toggle",
|
||||||
"opcode": 1,
|
"opcode": 1,
|
||||||
"raw_scalar_value": 1,
|
"raw_scalar_value": 0,
|
||||||
"value_byte_0x09": 0,
|
"value_byte_0x09": 0,
|
||||||
"value_dword_0x0d": 0,
|
"value_dword_0x0d": 0,
|
||||||
"value_byte_0x11": 0,
|
"value_byte_0x11": 0,
|
||||||
|
|
@ -80,7 +80,7 @@
|
||||||
"value_word_0x16": 0,
|
"value_word_0x16": 0,
|
||||||
"row_shape": "bool_toggle",
|
"row_shape": "bool_toggle",
|
||||||
"semantic_family": "bool_toggle",
|
"semantic_family": "bool_toggle",
|
||||||
"semantic_preview": "Set Territory - Allow All to TRUE",
|
"semantic_preview": "Set Territory - Allow All to FALSE",
|
||||||
"locomotive_name": null,
|
"locomotive_name": null,
|
||||||
"notes": []
|
"notes": []
|
||||||
}
|
}
|
||||||
|
|
@ -89,14 +89,13 @@
|
||||||
"decoded_actions": [],
|
"decoded_actions": [],
|
||||||
"executable_import_ready": false,
|
"executable_import_ready": false,
|
||||||
"notes": [
|
"notes": [
|
||||||
"decoded from grounded real 0x4e9a row framing",
|
"decoded from grounded real 0x4e9a row framing"
|
||||||
"territory policy mutation remains parity-only in this slice"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"notes": [
|
"notes": [
|
||||||
"real territory policy descriptor sample"
|
"unsupported real territory-access FALSE variant sample"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"fixture_id": "packed-event-territory-access-missing-scope-save-slice-fixture",
|
||||||
|
"source": {
|
||||||
|
"kind": "captured-runtime",
|
||||||
|
"description": "Fixture keeping the missing-scope Territory - Allow All variant explicit."
|
||||||
|
},
|
||||||
|
"state_save_slice_path": "packed-event-territory-access-missing-scope-save-slice.json",
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"kind": "service_trigger_kind",
|
||||||
|
"trigger_kind": 6
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"expected_summary": {
|
||||||
|
"packed_event_collection_present": true,
|
||||||
|
"packed_event_record_count": 1,
|
||||||
|
"packed_event_decoded_record_count": 1,
|
||||||
|
"packed_event_imported_runtime_record_count": 0,
|
||||||
|
"event_runtime_record_count": 0,
|
||||||
|
"packed_event_blocked_territory_access_scope_count": 1
|
||||||
|
},
|
||||||
|
"expected_state_fragment": {
|
||||||
|
"packed_event_collection": {
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"import_outcome": "blocked_territory_access_scope"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"save_slice_id": "packed-event-territory-access-missing-scope-save-slice",
|
||||||
|
"source": {
|
||||||
|
"description": "Tracked save-slice document with a TRUE Territory - Allow All row missing company or territory scope.",
|
||||||
|
"original_save_filename": "captured-territory-access-missing-scope.gms",
|
||||||
|
"original_save_sha256": "territory-access-missing-scope-sample-sha256",
|
||||||
|
"notes": [
|
||||||
|
"tracked as JSON save-slice document rather than raw .smp",
|
||||||
|
"keeps the descriptor 3 missing-scope boundary 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": 28928,
|
||||||
|
"records_tag_offset": 29184,
|
||||||
|
"close_tag_offset": 29696,
|
||||||
|
"packed_state_version": 1001,
|
||||||
|
"packed_state_version_hex": "0x000003e9",
|
||||||
|
"live_id_bound": 50,
|
||||||
|
"live_record_count": 1,
|
||||||
|
"live_entry_ids": [50],
|
||||||
|
"decoded_record_count": 1,
|
||||||
|
"imported_runtime_record_count": 0,
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"record_index": 0,
|
||||||
|
"live_entry_id": 50,
|
||||||
|
"payload_offset": 29400,
|
||||||
|
"payload_len": 132,
|
||||||
|
"decode_status": "parity_only",
|
||||||
|
"payload_family": "real_packed_v1",
|
||||||
|
"trigger_kind": 6,
|
||||||
|
"one_shot": false,
|
||||||
|
"compact_control": {
|
||||||
|
"mode_byte_0x7ef": 6,
|
||||||
|
"primary_selector_0x7f0": 12,
|
||||||
|
"grouped_mode_0x7f4": 2,
|
||||||
|
"one_shot_header_0x7f5": 0,
|
||||||
|
"modifier_flag_0x7f9": 0,
|
||||||
|
"modifier_flag_0x7fa": 0,
|
||||||
|
"grouped_target_scope_ordinals_0x7fb": [9, 1, 1, 1],
|
||||||
|
"grouped_scope_checkboxes_0x7ff": [1, 0, 0, 0],
|
||||||
|
"summary_toggle_0x800": 1,
|
||||||
|
"grouped_territory_selectors_0x80f": [-1, -1, -1, -1]
|
||||||
|
},
|
||||||
|
"text_bands": [],
|
||||||
|
"standalone_condition_row_count": 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": 3,
|
||||||
|
"descriptor_label": "Territory - Allow All",
|
||||||
|
"target_mask_bits": 5,
|
||||||
|
"parameter_family": "territory_access_toggle",
|
||||||
|
"opcode": 1,
|
||||||
|
"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 Territory - Allow All to TRUE",
|
||||||
|
"locomotive_name": null,
|
||||||
|
"notes": [
|
||||||
|
"territory access row is missing company or territory scope"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"decoded_conditions": [],
|
||||||
|
"decoded_actions": [],
|
||||||
|
"executable_import_ready": false,
|
||||||
|
"notes": [
|
||||||
|
"decoded from grounded real 0x4e9a row framing",
|
||||||
|
"descriptor 3 remains parity-only when company or territory scope is absent"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notes": [
|
||||||
|
"unsupported real territory-access missing-scope sample"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"fixture_id": "packed-event-territory-access-overlay-fixture",
|
||||||
|
"source": {
|
||||||
|
"kind": "captured-runtime",
|
||||||
|
"description": "Fixture proving descriptor 3 Territory - Allow All imports as company territory-access rights and executes through the ordinary runtime path."
|
||||||
|
},
|
||||||
|
"state_import_path": "packed-event-territory-access-overlay.json",
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"kind": "service_trigger_kind",
|
||||||
|
"trigger_kind": 6
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"expected_summary": {
|
||||||
|
"calendar_projection_source": "base-snapshot-preserved",
|
||||||
|
"calendar_projection_is_placeholder": false,
|
||||||
|
"company_count": 3,
|
||||||
|
"active_company_count": 3,
|
||||||
|
"player_count": 2,
|
||||||
|
"territory_count": 2,
|
||||||
|
"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": {
|
||||||
|
"company_territory_access": [
|
||||||
|
{
|
||||||
|
"company_id": 1,
|
||||||
|
"territory_id": 7
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packed_event_collection": {
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"import_outcome": "imported",
|
||||||
|
"decoded_actions": [
|
||||||
|
{
|
||||||
|
"kind": "set_company_territory_access",
|
||||||
|
"target": {
|
||||||
|
"kind": "selected_company"
|
||||||
|
},
|
||||||
|
"territory": {
|
||||||
|
"kind": "ids",
|
||||||
|
"ids": [7]
|
||||||
|
},
|
||||||
|
"value": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"event_runtime_records": [
|
||||||
|
{
|
||||||
|
"record_id": 48,
|
||||||
|
"service_count": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"import_id": "packed-event-territory-access-overlay",
|
||||||
|
"source": {
|
||||||
|
"description": "Overlay import combining company and territory runtime context with the real Territory - Allow All descriptor sample."
|
||||||
|
},
|
||||||
|
"base_snapshot_path": "packed-event-territory-player-overlay-base-snapshot.json",
|
||||||
|
"save_slice_path": "packed-event-territory-access-save-slice.json"
|
||||||
|
}
|
||||||
114
fixtures/runtime/packed-event-territory-access-save-slice.json
Normal file
114
fixtures/runtime/packed-event-territory-access-save-slice.json
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"save_slice_id": "packed-event-territory-access-save-slice",
|
||||||
|
"source": {
|
||||||
|
"description": "Tracked save-slice document with a real Territory - Allow All row interpreted as company territory-access rights.",
|
||||||
|
"original_save_filename": "captured-territory-access.gms",
|
||||||
|
"original_save_sha256": "territory-access-sample-sha256",
|
||||||
|
"notes": [
|
||||||
|
"tracked as JSON save-slice document rather than raw .smp",
|
||||||
|
"locks descriptor 3 import as company-to-territory access rights instead of a territory-owned policy bit"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"save_slice": {
|
||||||
|
"file_extension_hint": "gms",
|
||||||
|
"container_profile_family": "rt3-classic-save-container-v1",
|
||||||
|
"mechanism_family": "classic-save-rehydrate-v1",
|
||||||
|
"mechanism_confidence": "grounded",
|
||||||
|
"trailer_family": null,
|
||||||
|
"bridge_family": null,
|
||||||
|
"profile": null,
|
||||||
|
"candidate_availability_table": null,
|
||||||
|
"special_conditions_table": null,
|
||||||
|
"event_runtime_collection": {
|
||||||
|
"source_kind": "packed-event-runtime-collection",
|
||||||
|
"mechanism_family": "classic-save-rehydrate-v1",
|
||||||
|
"mechanism_confidence": "grounded",
|
||||||
|
"container_profile_family": "rt3-classic-save-container-v1",
|
||||||
|
"metadata_tag_offset": 28928,
|
||||||
|
"records_tag_offset": 29184,
|
||||||
|
"close_tag_offset": 29696,
|
||||||
|
"packed_state_version": 1001,
|
||||||
|
"packed_state_version_hex": "0x000003e9",
|
||||||
|
"live_id_bound": 48,
|
||||||
|
"live_record_count": 1,
|
||||||
|
"live_entry_ids": [48],
|
||||||
|
"decoded_record_count": 1,
|
||||||
|
"imported_runtime_record_count": 1,
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"record_index": 0,
|
||||||
|
"live_entry_id": 48,
|
||||||
|
"payload_offset": 29320,
|
||||||
|
"payload_len": 132,
|
||||||
|
"decode_status": "parity_only",
|
||||||
|
"payload_family": "real_packed_v1",
|
||||||
|
"trigger_kind": 6,
|
||||||
|
"one_shot": false,
|
||||||
|
"compact_control": {
|
||||||
|
"mode_byte_0x7ef": 6,
|
||||||
|
"primary_selector_0x7f0": 12,
|
||||||
|
"grouped_mode_0x7f4": 2,
|
||||||
|
"one_shot_header_0x7f5": 0,
|
||||||
|
"modifier_flag_0x7f9": 0,
|
||||||
|
"modifier_flag_0x7fa": 0,
|
||||||
|
"grouped_target_scope_ordinals_0x7fb": [1, 1, 1, 1],
|
||||||
|
"grouped_scope_checkboxes_0x7ff": [1, 0, 0, 0],
|
||||||
|
"summary_toggle_0x800": 1,
|
||||||
|
"grouped_territory_selectors_0x80f": [7, -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": 3,
|
||||||
|
"descriptor_label": "Territory - Allow All",
|
||||||
|
"target_mask_bits": 5,
|
||||||
|
"parameter_family": "territory_access_toggle",
|
||||||
|
"opcode": 1,
|
||||||
|
"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 Territory - Allow All to TRUE",
|
||||||
|
"locomotive_name": null,
|
||||||
|
"notes": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"decoded_conditions": [],
|
||||||
|
"decoded_actions": [
|
||||||
|
{
|
||||||
|
"kind": "set_company_territory_access",
|
||||||
|
"target": {
|
||||||
|
"kind": "selected_company"
|
||||||
|
},
|
||||||
|
"territory": {
|
||||||
|
"kind": "ids",
|
||||||
|
"ids": [7]
|
||||||
|
},
|
||||||
|
"value": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"executable_import_ready": true,
|
||||||
|
"notes": [
|
||||||
|
"decoded from grounded real 0x4e9a row framing",
|
||||||
|
"descriptor 3 now lowers to company territory-access grants when company and territory scope are both explicit"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notes": [
|
||||||
|
"real territory-access descriptor sample"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
{
|
|
||||||
"format_version": 1,
|
|
||||||
"fixture_id": "packed-event-territory-policy-save-slice-fixture",
|
|
||||||
"source": {
|
|
||||||
"kind": "captured-runtime",
|
|
||||||
"description": "Fixture proving descriptor 3 Territory - Allow All stays parity-only with an explicit blocker."
|
|
||||||
},
|
|
||||||
"state_save_slice_path": "packed-event-territory-policy-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_parity_only_record_count": 1,
|
|
||||||
"packed_event_blocked_territory_policy_descriptor_count": 1,
|
|
||||||
"event_runtime_record_count": 0
|
|
||||||
},
|
|
||||||
"expected_state_fragment": {
|
|
||||||
"packed_event_collection": {
|
|
||||||
"records": [
|
|
||||||
{
|
|
||||||
"import_outcome": "blocked_territory_policy_descriptor",
|
|
||||||
"grouped_effect_rows": [
|
|
||||||
{
|
|
||||||
"descriptor_label": "Territory - Allow All"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue