Implement real company-scoped event descriptors
This commit is contained in:
parent
eb6c4833af
commit
780e739daa
21 changed files with 1483 additions and 56 deletions
13
README.md
13
README.md
|
|
@ -13,12 +13,13 @@ runtime rehost layer that can execute deterministic world work, compare normaliz
|
|||
subsystem breadth without depending on the shell or presentation path. The current packed-event
|
||||
frontier is broader real grouped-descriptor coverage on top of the existing save-slice, snapshot,
|
||||
overlay-import, compact-control, and symbolic company-target workflows. The runtime already carries
|
||||
selected-company and controller-role context through overlay imports, real descriptor `2`
|
||||
`Company Cash` now parses and executes through the ordinary runtime path, and synthetic packed
|
||||
records still exercise the same service engine without a parallel packed executor. Condition-
|
||||
relative company scopes remain explicitly blocked until condition evaluation is grounded. The PE32
|
||||
hook remains useful as capture and integration tooling, but it is no longer the main execution
|
||||
milestone.
|
||||
selected-company and controller-role context through overlay imports, and real descriptors `2`
|
||||
`Company Cash`, `13` `Deactivate Company`, and `16` `Company Track Pieces Buildable` now parse and
|
||||
execute through the ordinary runtime path. Synthetic packed records still exercise the same service
|
||||
engine without a parallel packed executor. Condition-relative company scopes remain explicitly
|
||||
blocked until condition evaluation is grounded, and mixed supported/unsupported real rows stay
|
||||
parity-only. The PE32 hook remains useful as capture and integration tooling, but it is no longer
|
||||
the main execution milestone.
|
||||
|
||||
## Project Docs
|
||||
|
||||
|
|
|
|||
|
|
@ -4442,6 +4442,12 @@ mod tests {
|
|||
.join("../../fixtures/runtime/packed-event-selective-import-overlay-fixture.json");
|
||||
let symbolic_overlay_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../../fixtures/runtime/packed-event-symbolic-company-scope-overlay-fixture.json");
|
||||
let deactivate_overlay_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../../fixtures/runtime/packed-event-deactivate-company-overlay-fixture.json");
|
||||
let track_capacity_overlay_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../../fixtures/runtime/packed-event-track-capacity-overlay-fixture.json");
|
||||
let mixed_overlay_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../../fixtures/runtime/packed-event-mixed-company-descriptor-overlay-fixture.json");
|
||||
|
||||
run_runtime_summarize_fixture(&parity_fixture)
|
||||
.expect("save-slice-backed parity fixture should summarize");
|
||||
|
|
@ -4451,6 +4457,12 @@ mod tests {
|
|||
.expect("overlay-backed selective-import fixture should summarize");
|
||||
run_runtime_summarize_fixture(&symbolic_overlay_fixture)
|
||||
.expect("overlay-backed symbolic-target fixture should summarize");
|
||||
run_runtime_summarize_fixture(&deactivate_overlay_fixture)
|
||||
.expect("overlay-backed deactivate-company fixture should summarize");
|
||||
run_runtime_summarize_fixture(&track_capacity_overlay_fixture)
|
||||
.expect("overlay-backed track-capacity fixture should summarize");
|
||||
run_runtime_summarize_fixture(&mixed_overlay_fixture)
|
||||
.expect("overlay-backed mixed real-row fixture should summarize");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -330,6 +330,8 @@ mod tests {
|
|||
controller_kind: rrt_runtime::RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 100,
|
||||
debt: 0,
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
}],
|
||||
selected_company_id: Some(42),
|
||||
packed_event_collection: None,
|
||||
|
|
|
|||
|
|
@ -62,6 +62,8 @@ pub struct ExpectedRuntimeSummary {
|
|||
#[serde(default)]
|
||||
pub company_count: Option<usize>,
|
||||
#[serde(default)]
|
||||
pub active_company_count: Option<usize>,
|
||||
#[serde(default)]
|
||||
pub packed_event_collection_present: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub packed_event_record_count: Option<usize>,
|
||||
|
|
@ -327,6 +329,14 @@ impl ExpectedRuntimeSummary {
|
|||
));
|
||||
}
|
||||
}
|
||||
if let Some(count) = self.active_company_count {
|
||||
if actual.active_company_count != count {
|
||||
mismatches.push(format!(
|
||||
"active_company_count mismatch: expected {count}, got {}",
|
||||
actual.active_company_count
|
||||
));
|
||||
}
|
||||
}
|
||||
if let Some(present) = self.packed_event_collection_present {
|
||||
if actual.packed_event_collection_present != present {
|
||||
mismatches.push(format!(
|
||||
|
|
|
|||
|
|
@ -710,7 +710,7 @@ fn smp_packed_record_to_runtime_event_record(
|
|||
if record.decode_status == "unsupported_framing" {
|
||||
return None;
|
||||
}
|
||||
if record.payload_family == "real_packed_v1" && record.decoded_actions.is_empty() {
|
||||
if record.payload_family == "real_packed_v1" && !record.executable_import_ready {
|
||||
return None;
|
||||
}
|
||||
|
||||
|
|
@ -771,6 +771,25 @@ fn smp_runtime_effect_to_runtime_effect(
|
|||
Err(company_target_import_error_message(target, company_context))
|
||||
}
|
||||
}
|
||||
RuntimeEffect::DeactivateCompany { target } => {
|
||||
if company_target_import_blocker(target, company_context).is_none() {
|
||||
Ok(RuntimeEffect::DeactivateCompany {
|
||||
target: target.clone(),
|
||||
})
|
||||
} else {
|
||||
Err(company_target_import_error_message(target, company_context))
|
||||
}
|
||||
}
|
||||
RuntimeEffect::SetCompanyTrackLayingCapacity { target, value } => {
|
||||
if company_target_import_blocker(target, company_context).is_none() {
|
||||
Ok(RuntimeEffect::SetCompanyTrackLayingCapacity {
|
||||
target: target.clone(),
|
||||
value: *value,
|
||||
})
|
||||
} else {
|
||||
Err(company_target_import_error_message(target, company_context))
|
||||
}
|
||||
}
|
||||
RuntimeEffect::AdjustCompanyCash { target, delta } => {
|
||||
if company_target_import_blocker(target, company_context).is_none() {
|
||||
Ok(RuntimeEffect::AdjustCompanyCash {
|
||||
|
|
@ -912,18 +931,15 @@ fn determine_packed_event_import_outcome(
|
|||
if record.compact_control.is_none() {
|
||||
return "blocked_missing_compact_control".to_string();
|
||||
}
|
||||
if !record.executable_import_ready {
|
||||
return "blocked_unmapped_real_descriptor".to_string();
|
||||
}
|
||||
if let Some(blocker) = packed_record_company_target_import_blocker(record, company_context)
|
||||
{
|
||||
return company_target_import_outcome(blocker).to_string();
|
||||
}
|
||||
if !record.decoded_actions.is_empty() {
|
||||
return "blocked_unsupported_decode".to_string();
|
||||
}
|
||||
if let Some(blocker) = real_record_company_target_import_blocker(record, company_context) {
|
||||
return company_target_import_outcome(blocker).to_string();
|
||||
}
|
||||
return "blocked_unmapped_real_descriptor".to_string();
|
||||
}
|
||||
if let Some(blocker) = packed_record_company_target_import_blocker(record, company_context) {
|
||||
return company_target_import_outcome(blocker).to_string();
|
||||
}
|
||||
|
|
@ -946,6 +962,8 @@ fn runtime_effect_company_target_import_blocker(
|
|||
) -> Option<CompanyTargetImportBlocker> {
|
||||
match effect {
|
||||
RuntimeEffect::SetCompanyCash { target, .. }
|
||||
| RuntimeEffect::DeactivateCompany { target }
|
||||
| RuntimeEffect::SetCompanyTrackLayingCapacity { target, .. }
|
||||
| RuntimeEffect::AdjustCompanyCash { target, .. }
|
||||
| RuntimeEffect::AdjustCompanyDebt { target, .. } => {
|
||||
company_target_import_blocker(target, company_context)
|
||||
|
|
@ -996,16 +1014,6 @@ fn classify_real_grouped_company_target(ordinal: u8) -> Option<RuntimeCompanyTar
|
|||
}
|
||||
}
|
||||
|
||||
fn real_record_company_target_import_blocker(
|
||||
record: &SmpLoadedPackedEventRecordSummary,
|
||||
company_context: &ImportCompanyContext,
|
||||
) -> Option<CompanyTargetImportBlocker> {
|
||||
classify_real_grouped_company_targets(record)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.find_map(|target| company_target_import_blocker(&target, company_context))
|
||||
}
|
||||
|
||||
fn company_target_import_outcome(blocker: CompanyTargetImportBlocker) -> &'static str {
|
||||
match blocker {
|
||||
CompanyTargetImportBlocker::MissingCompanyContext => "blocked_missing_company_context",
|
||||
|
|
@ -1417,6 +1425,83 @@ mod tests {
|
|||
}]
|
||||
}
|
||||
|
||||
fn real_deactivate_company_row(enabled: bool) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||
crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||
group_index: 0,
|
||||
row_index: 0,
|
||||
descriptor_id: 13,
|
||||
descriptor_label: Some("Deactivate Company".to_string()),
|
||||
target_mask_bits: Some(0x01),
|
||||
parameter_family: Some("company_lifecycle_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 Deactivate Company to {}",
|
||||
if enabled { "TRUE" } else { "FALSE" }
|
||||
)),
|
||||
locomotive_name: None,
|
||||
notes: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn real_track_capacity_row(value: i32) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||
crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||
group_index: 0,
|
||||
row_index: 0,
|
||||
descriptor_id: 16,
|
||||
descriptor_label: Some("Company Track Pieces Buildable".to_string()),
|
||||
target_mask_bits: Some(0x01),
|
||||
parameter_family: Some("company_build_limit_scalar".to_string()),
|
||||
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 Company Track Pieces Buildable to {value}"
|
||||
)),
|
||||
locomotive_name: None,
|
||||
notes: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn unsupported_real_grouped_row() -> crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||
crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||
group_index: 1,
|
||||
row_index: 0,
|
||||
descriptor_id: 8,
|
||||
descriptor_label: Some("Economic Status".to_string()),
|
||||
target_mask_bits: Some(0x08),
|
||||
parameter_family: Some("whole_game_state_enum".to_string()),
|
||||
opcode: 3,
|
||||
raw_scalar_value: 2,
|
||||
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("Set Economic Status to 2".to_string()),
|
||||
locomotive_name: None,
|
||||
notes: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn real_compact_control() -> crate::SmpLoadedPackedEventCompactControlSummary {
|
||||
crate::SmpLoadedPackedEventCompactControlSummary {
|
||||
mode_byte_0x7ef: 6,
|
||||
|
|
@ -2173,12 +2258,16 @@ mod tests {
|
|||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 100,
|
||||
debt: 10,
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
},
|
||||
crate::RuntimeCompany {
|
||||
company_id: 2,
|
||||
controller_kind: RuntimeCompanyControllerKind::Ai,
|
||||
current_cash: 50,
|
||||
debt: 20,
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
},
|
||||
],
|
||||
selected_company_id: Some(1),
|
||||
|
|
@ -2312,7 +2401,7 @@ mod tests {
|
|||
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||
value: 7,
|
||||
}],
|
||||
executable_import_ready: false,
|
||||
executable_import_ready: true,
|
||||
notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()],
|
||||
}],
|
||||
}),
|
||||
|
|
@ -2398,8 +2487,11 @@ mod tests {
|
|||
standalone_condition_rows: real_condition_rows(),
|
||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: real_grouped_rows(),
|
||||
decoded_actions: vec![],
|
||||
executable_import_ready: false,
|
||||
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
||||
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||
value: 7,
|
||||
}],
|
||||
executable_import_ready: true,
|
||||
notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()],
|
||||
}],
|
||||
}),
|
||||
|
|
@ -2521,6 +2613,8 @@ mod tests {
|
|||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 500,
|
||||
debt: 20,
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
}],
|
||||
selected_company_id: Some(42),
|
||||
packed_event_collection: None,
|
||||
|
|
@ -2610,7 +2704,7 @@ mod tests {
|
|||
target: RuntimeCompanyTarget::SelectedCompany,
|
||||
value: 250,
|
||||
}],
|
||||
executable_import_ready: false,
|
||||
executable_import_ready: true,
|
||||
notes: vec![
|
||||
"decoded from grounded real 0x4e9a row framing".to_string(),
|
||||
"grouped descriptor labels and target masks come from the checked-in effect table recovered around 0x006103a0".to_string(),
|
||||
|
|
@ -2647,6 +2741,381 @@ mod tests {
|
|||
assert_eq!(import.state.companies[0].current_cash, 250);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overlays_real_deactivate_company_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,
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
}],
|
||||
selected_company_id: Some(42),
|
||||
..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: 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(7),
|
||||
active: None,
|
||||
marks_collection_dirty: None,
|
||||
one_shot: Some(false),
|
||||
compact_control: Some(crate::SmpLoadedPackedEventCompactControlSummary {
|
||||
mode_byte_0x7ef: 7,
|
||||
primary_selector_0x7f0: 0x63,
|
||||
grouped_mode_0x7f4: 2,
|
||||
one_shot_header_0x7f5: 0,
|
||||
modifier_flag_0x7f9: 1,
|
||||
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![-1, -1, -1, -1],
|
||||
}),
|
||||
text_bands: packed_text_bands(),
|
||||
standalone_condition_row_count: 0,
|
||||
standalone_condition_rows: vec![],
|
||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: vec![real_deactivate_company_row(true)],
|
||||
decoded_actions: vec![RuntimeEffect::DeactivateCompany {
|
||||
target: RuntimeCompanyTarget::SelectedCompany,
|
||||
}],
|
||||
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-deactivate-company-overlay",
|
||||
None,
|
||||
)
|
||||
.expect("overlay import should project");
|
||||
|
||||
assert_eq!(import.state.event_runtime_records.len(), 1);
|
||||
execute_step_command(
|
||||
&mut import.state,
|
||||
&StepCommand::ServiceTriggerKind { trigger_kind: 7 },
|
||||
)
|
||||
.expect("real deactivate-company descriptor should execute");
|
||||
|
||||
assert!(!import.state.companies[0].active);
|
||||
assert_eq!(import.state.selected_company_id, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keeps_real_deactivate_company_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: 14,
|
||||
live_record_count: 1,
|
||||
live_entry_ids: vec![14],
|
||||
decoded_record_count: 1,
|
||||
imported_runtime_record_count: 0,
|
||||
records: vec![crate::SmpLoadedPackedEventRecordSummary {
|
||||
record_index: 0,
|
||||
live_entry_id: 14,
|
||||
payload_offset: Some(0x7202),
|
||||
payload_len: Some(120),
|
||||
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(crate::SmpLoadedPackedEventCompactControlSummary {
|
||||
mode_byte_0x7ef: 7,
|
||||
primary_selector_0x7f0: 0x63,
|
||||
grouped_mode_0x7f4: 2,
|
||||
one_shot_header_0x7f5: 0,
|
||||
modifier_flag_0x7f9: 1,
|
||||
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![-1, -1, -1, -1],
|
||||
}),
|
||||
text_bands: packed_text_bands(),
|
||||
standalone_condition_row_count: 0,
|
||||
standalone_condition_rows: vec![],
|
||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: vec![real_deactivate_company_row(false)],
|
||||
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-deactivate-company-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_unmapped_real_descriptor")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overlays_real_track_capacity_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,
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
}],
|
||||
selected_company_id: Some(42),
|
||||
..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: 16,
|
||||
live_record_count: 1,
|
||||
live_entry_ids: vec![16],
|
||||
decoded_record_count: 1,
|
||||
imported_runtime_record_count: 0,
|
||||
records: vec![crate::SmpLoadedPackedEventRecordSummary {
|
||||
record_index: 0,
|
||||
live_entry_id: 16,
|
||||
payload_offset: Some(0x7202),
|
||||
payload_len: Some(120),
|
||||
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(crate::SmpLoadedPackedEventCompactControlSummary {
|
||||
mode_byte_0x7ef: 7,
|
||||
primary_selector_0x7f0: 0x63,
|
||||
grouped_mode_0x7f4: 2,
|
||||
one_shot_header_0x7f5: 0,
|
||||
modifier_flag_0x7f9: 1,
|
||||
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![-1, -1, -1, -1],
|
||||
}),
|
||||
text_bands: packed_text_bands(),
|
||||
standalone_condition_row_count: 0,
|
||||
standalone_condition_rows: vec![],
|
||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||
grouped_effect_rows: vec![real_track_capacity_row(18)],
|
||||
decoded_actions: vec![RuntimeEffect::SetCompanyTrackLayingCapacity {
|
||||
target: RuntimeCompanyTarget::SelectedCompany,
|
||||
value: Some(18),
|
||||
}],
|
||||
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-track-capacity-overlay",
|
||||
None,
|
||||
)
|
||||
.expect("overlay import should project");
|
||||
|
||||
execute_step_command(
|
||||
&mut import.state,
|
||||
&StepCommand::ServiceTriggerKind { trigger_kind: 7 },
|
||||
)
|
||||
.expect("real track-capacity descriptor should execute");
|
||||
|
||||
assert_eq!(
|
||||
import.state.companies[0].available_track_laying_capacity,
|
||||
Some(18)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keeps_mixed_real_records_out_of_event_runtime_records() {
|
||||
let base_state = RuntimeState {
|
||||
companies: vec![crate::RuntimeCompany {
|
||||
company_id: 42,
|
||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 500,
|
||||
debt: 20,
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
}],
|
||||
selected_company_id: Some(42),
|
||||
..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: 17,
|
||||
live_record_count: 1,
|
||||
live_entry_ids: vec![17],
|
||||
decoded_record_count: 1,
|
||||
imported_runtime_record_count: 0,
|
||||
records: vec![crate::SmpLoadedPackedEventRecordSummary {
|
||||
record_index: 0,
|
||||
live_entry_id: 17,
|
||||
payload_offset: Some(0x7202),
|
||||
payload_len: Some(160),
|
||||
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(crate::SmpLoadedPackedEventCompactControlSummary {
|
||||
mode_byte_0x7ef: 7,
|
||||
primary_selector_0x7f0: 0x63,
|
||||
grouped_mode_0x7f4: 2,
|
||||
one_shot_header_0x7f5: 0,
|
||||
modifier_flag_0x7f9: 1,
|
||||
modifier_flag_0x7fa: 0,
|
||||
grouped_target_scope_ordinals_0x7fb: vec![1, 1, 1, 1],
|
||||
grouped_scope_checkboxes_0x7ff: vec![1, 1, 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![],
|
||||
grouped_effect_row_counts: vec![1, 1, 0, 0],
|
||||
grouped_effect_rows: vec![
|
||||
real_track_capacity_row(18),
|
||||
unsupported_real_grouped_row(),
|
||||
],
|
||||
decoded_actions: vec![RuntimeEffect::SetCompanyTrackLayingCapacity {
|
||||
target: RuntimeCompanyTarget::SelectedCompany,
|
||||
value: Some(18),
|
||||
}],
|
||||
executable_import_ready: false,
|
||||
notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()],
|
||||
}],
|
||||
}),
|
||||
notes: vec![],
|
||||
};
|
||||
|
||||
let import = project_save_slice_overlay_to_runtime_state_import(
|
||||
&base_state,
|
||||
&save_slice,
|
||||
"mixed-real-record-overlay",
|
||||
None,
|
||||
)
|
||||
.expect("overlay import 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_unmapped_real_descriptor")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overlays_save_slice_events_onto_base_company_context() {
|
||||
let base_state = RuntimeState {
|
||||
|
|
@ -2665,6 +3134,8 @@ mod tests {
|
|||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 500,
|
||||
debt: 20,
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
}],
|
||||
selected_company_id: Some(42),
|
||||
packed_event_collection: None,
|
||||
|
|
@ -2827,6 +3298,8 @@ mod tests {
|
|||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 100,
|
||||
debt: 0,
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
}],
|
||||
selected_company_id: Some(42),
|
||||
packed_event_collection: None,
|
||||
|
|
|
|||
|
|
@ -13,11 +13,19 @@ pub enum RuntimeCompanyControllerKind {
|
|||
Ai,
|
||||
}
|
||||
|
||||
fn runtime_company_default_active() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct RuntimeCompany {
|
||||
pub company_id: u32,
|
||||
pub current_cash: i64,
|
||||
pub debt: u64,
|
||||
#[serde(default = "runtime_company_default_active")]
|
||||
pub active: bool,
|
||||
#[serde(default)]
|
||||
pub available_track_laying_capacity: Option<u32>,
|
||||
#[serde(default)]
|
||||
pub controller_kind: RuntimeCompanyControllerKind,
|
||||
}
|
||||
|
|
@ -44,6 +52,13 @@ pub enum RuntimeEffect {
|
|||
target: RuntimeCompanyTarget,
|
||||
value: i64,
|
||||
},
|
||||
DeactivateCompany {
|
||||
target: RuntimeCompanyTarget,
|
||||
},
|
||||
SetCompanyTrackLayingCapacity {
|
||||
target: RuntimeCompanyTarget,
|
||||
value: Option<u32>,
|
||||
},
|
||||
AdjustCompanyCash {
|
||||
target: RuntimeCompanyTarget,
|
||||
delta: i64,
|
||||
|
|
@ -352,10 +367,14 @@ impl RuntimeState {
|
|||
self.calendar.validate()?;
|
||||
|
||||
let mut seen_company_ids = BTreeSet::new();
|
||||
let mut active_company_ids = BTreeSet::new();
|
||||
for company in &self.companies {
|
||||
if !seen_company_ids.insert(company.company_id) {
|
||||
return Err(format!("duplicate company_id {}", company.company_id));
|
||||
}
|
||||
if company.active {
|
||||
active_company_ids.insert(company.company_id);
|
||||
}
|
||||
}
|
||||
if let Some(selected_company_id) = self.selected_company_id {
|
||||
if !seen_company_ids.contains(&selected_company_id) {
|
||||
|
|
@ -364,6 +383,12 @@ impl RuntimeState {
|
|||
selected_company_id
|
||||
));
|
||||
}
|
||||
if !active_company_ids.contains(&selected_company_id) {
|
||||
return Err(format!(
|
||||
"selected_company_id {} must reference an active company",
|
||||
selected_company_id
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let mut seen_record_ids = BTreeSet::new();
|
||||
|
|
@ -669,6 +694,8 @@ fn validate_runtime_effect(
|
|||
}
|
||||
}
|
||||
RuntimeEffect::SetCompanyCash { target, .. }
|
||||
| RuntimeEffect::DeactivateCompany { target }
|
||||
| RuntimeEffect::SetCompanyTrackLayingCapacity { target, .. }
|
||||
| RuntimeEffect::AdjustCompanyCash { target, .. }
|
||||
| RuntimeEffect::AdjustCompanyDebt { target, .. } => {
|
||||
validate_company_target(target, valid_company_ids)?;
|
||||
|
|
@ -756,12 +783,16 @@ mod tests {
|
|||
company_id: 1,
|
||||
current_cash: 100,
|
||||
debt: 0,
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||
},
|
||||
RuntimeCompany {
|
||||
company_id: 1,
|
||||
current_cash: 200,
|
||||
debt: 0,
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||
},
|
||||
],
|
||||
|
|
@ -840,6 +871,8 @@ mod tests {
|
|||
company_id: 1,
|
||||
current_cash: 100,
|
||||
debt: 0,
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||
}],
|
||||
selected_company_id: None,
|
||||
|
|
@ -882,6 +915,8 @@ mod tests {
|
|||
company_id: 1,
|
||||
current_cash: 100,
|
||||
debt: 0,
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||
}],
|
||||
selected_company_id: None,
|
||||
|
|
@ -1018,6 +1053,8 @@ mod tests {
|
|||
company_id: 1,
|
||||
current_cash: 100,
|
||||
debt: 0,
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
}],
|
||||
selected_company_id: Some(2),
|
||||
|
|
@ -1030,4 +1067,36 @@ mod tests {
|
|||
|
||||
assert!(state.validate().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_selected_company_id_that_is_inactive() {
|
||||
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,
|
||||
active: false,
|
||||
available_track_laying_capacity: None,
|
||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
}],
|
||||
selected_company_id: Some(1),
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetad
|
|||
label: "Deactivate Company",
|
||||
target_mask_bits: 0x01,
|
||||
parameter_family: "company_lifecycle_toggle",
|
||||
executable_in_runtime: false,
|
||||
executable_in_runtime: true,
|
||||
},
|
||||
RealGroupedEffectDescriptorMetadata {
|
||||
descriptor_id: 15,
|
||||
|
|
@ -177,7 +177,7 @@ const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetad
|
|||
label: "Company Track Pieces Buildable",
|
||||
target_mask_bits: 0x01,
|
||||
parameter_family: "company_build_limit_scalar",
|
||||
executable_in_runtime: false,
|
||||
executable_in_runtime: true,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
@ -1944,7 +1944,8 @@ fn parse_real_event_runtime_record_summary(
|
|||
.as_ref()
|
||||
.map(|control| decode_real_grouped_effect_actions(&grouped_effect_rows, control))
|
||||
.unwrap_or_default();
|
||||
let executable_import_ready = !decoded_actions.is_empty()
|
||||
let executable_import_ready = !grouped_effect_rows.is_empty()
|
||||
&& decoded_actions.len() == grouped_effect_rows.len()
|
||||
&& decoded_actions
|
||||
.iter()
|
||||
.all(runtime_effect_supported_for_save_import);
|
||||
|
|
@ -2265,6 +2266,25 @@ fn decode_real_grouped_effect_action(
|
|||
});
|
||||
}
|
||||
|
||||
if descriptor_metadata.executable_in_runtime
|
||||
&& descriptor_metadata.descriptor_id == 13
|
||||
&& row.row_shape == "bool_toggle"
|
||||
&& row.raw_scalar_value != 0
|
||||
{
|
||||
return Some(RuntimeEffect::DeactivateCompany { target });
|
||||
}
|
||||
|
||||
if descriptor_metadata.executable_in_runtime
|
||||
&& descriptor_metadata.descriptor_id == 16
|
||||
&& row.row_shape == "scalar_assignment"
|
||||
&& row.raw_scalar_value >= 0
|
||||
{
|
||||
return Some(RuntimeEffect::SetCompanyTrackLayingCapacity {
|
||||
target,
|
||||
value: Some(row.raw_scalar_value as u32),
|
||||
});
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
|
|
@ -2417,14 +2437,22 @@ fn runtime_effect_supported_for_save_import(effect: &RuntimeEffect) -> bool {
|
|||
RuntimeEffect::SetWorldFlag { .. }
|
||||
| RuntimeEffect::SetCandidateAvailability { .. }
|
||||
| RuntimeEffect::SetSpecialCondition { .. }
|
||||
| RuntimeEffect::DeactivateCompany { .. }
|
||||
| RuntimeEffect::SetCompanyTrackLayingCapacity { .. }
|
||||
| RuntimeEffect::ActivateEventRecord { .. }
|
||||
| RuntimeEffect::DeactivateEventRecord { .. }
|
||||
| RuntimeEffect::RemoveEventRecord { .. } => true,
|
||||
RuntimeEffect::SetCompanyCash { target, .. }
|
||||
| RuntimeEffect::AdjustCompanyCash { target, .. }
|
||||
| RuntimeEffect::AdjustCompanyDebt { target, .. } => {
|
||||
matches!(target, RuntimeCompanyTarget::AllActive)
|
||||
}
|
||||
| RuntimeEffect::AdjustCompanyDebt { target, .. } => matches!(
|
||||
target,
|
||||
RuntimeCompanyTarget::AllActive
|
||||
| RuntimeCompanyTarget::Ids { .. }
|
||||
| RuntimeCompanyTarget::HumanCompanies
|
||||
| RuntimeCompanyTarget::AiCompanies
|
||||
| RuntimeCompanyTarget::SelectedCompany
|
||||
| RuntimeCompanyTarget::ConditionTrueCompany
|
||||
),
|
||||
RuntimeEffect::AppendEventRecord { record } => record
|
||||
.effects
|
||||
.iter()
|
||||
|
|
@ -7513,10 +7541,10 @@ mod tests {
|
|||
.expect("event runtime collection summary should parse");
|
||||
|
||||
assert_eq!(summary.decoded_record_count, 1);
|
||||
assert_eq!(summary.imported_runtime_record_count, 0);
|
||||
assert_eq!(summary.records[0].decode_status, "parity_only");
|
||||
assert_eq!(summary.imported_runtime_record_count, 1);
|
||||
assert_eq!(summary.records[0].decode_status, "executable");
|
||||
assert_eq!(summary.records[0].payload_family, "synthetic_harness");
|
||||
assert!(!summary.records[0].executable_import_ready);
|
||||
assert!(summary.records[0].executable_import_ready);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -299,6 +299,41 @@ fn apply_runtime_effects(
|
|||
mutated_company_ids.insert(company_id);
|
||||
}
|
||||
}
|
||||
RuntimeEffect::DeactivateCompany { target } => {
|
||||
let company_ids = resolve_company_target_ids(state, target)?;
|
||||
for company_id in company_ids {
|
||||
let company = state
|
||||
.companies
|
||||
.iter_mut()
|
||||
.find(|company| company.company_id == company_id)
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"missing company_id {company_id} while applying deactivate effect"
|
||||
)
|
||||
})?;
|
||||
company.active = false;
|
||||
mutated_company_ids.insert(company_id);
|
||||
if state.selected_company_id == Some(company_id) {
|
||||
state.selected_company_id = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
RuntimeEffect::SetCompanyTrackLayingCapacity { target, value } => {
|
||||
let company_ids = resolve_company_target_ids(state, target)?;
|
||||
for company_id in company_ids {
|
||||
let company = state
|
||||
.companies
|
||||
.iter_mut()
|
||||
.find(|company| company.company_id == company_id)
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"missing company_id {company_id} while applying track capacity effect"
|
||||
)
|
||||
})?;
|
||||
company.available_track_laying_capacity = *value;
|
||||
mutated_company_ids.insert(company_id);
|
||||
}
|
||||
}
|
||||
RuntimeEffect::AdjustCompanyCash { target, delta } => {
|
||||
let company_ids = resolve_company_target_ids(state, target)?;
|
||||
for company_id in company_ids {
|
||||
|
|
@ -429,6 +464,7 @@ fn resolve_company_target_ids(
|
|||
RuntimeCompanyTarget::AllActive => Ok(state
|
||||
.companies
|
||||
.iter()
|
||||
.filter(|company| company.active)
|
||||
.map(|company| company.company_id)
|
||||
.collect()),
|
||||
RuntimeCompanyTarget::Ids { ids } => {
|
||||
|
|
@ -458,7 +494,10 @@ fn resolve_company_target_ids(
|
|||
Ok(state
|
||||
.companies
|
||||
.iter()
|
||||
.filter(|company| company.controller_kind == RuntimeCompanyControllerKind::Human)
|
||||
.filter(|company| {
|
||||
company.active
|
||||
&& company.controller_kind == RuntimeCompanyControllerKind::Human
|
||||
})
|
||||
.map(|company| company.company_id)
|
||||
.collect())
|
||||
}
|
||||
|
|
@ -476,14 +515,27 @@ fn resolve_company_target_ids(
|
|||
Ok(state
|
||||
.companies
|
||||
.iter()
|
||||
.filter(|company| company.controller_kind == RuntimeCompanyControllerKind::Ai)
|
||||
.filter(|company| {
|
||||
company.active && company.controller_kind == RuntimeCompanyControllerKind::Ai
|
||||
})
|
||||
.map(|company| company.company_id)
|
||||
.collect())
|
||||
}
|
||||
RuntimeCompanyTarget::SelectedCompany => state
|
||||
RuntimeCompanyTarget::SelectedCompany => {
|
||||
let selected_company_id = state
|
||||
.selected_company_id
|
||||
.map(|company_id| vec![company_id])
|
||||
.ok_or_else(|| "target requires selected_company_id context".to_string()),
|
||||
.ok_or_else(|| "target requires selected_company_id context".to_string())?;
|
||||
if state
|
||||
.companies
|
||||
.iter()
|
||||
.any(|company| company.company_id == selected_company_id && company.active)
|
||||
{
|
||||
Ok(vec![selected_company_id])
|
||||
} else {
|
||||
Err("target requires selected_company_id to reference an active company"
|
||||
.to_string())
|
||||
}
|
||||
}
|
||||
RuntimeCompanyTarget::ConditionTrueCompany => {
|
||||
Err("target requires condition-evaluation context".to_string())
|
||||
}
|
||||
|
|
@ -530,6 +582,8 @@ mod tests {
|
|||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||
current_cash: 10,
|
||||
debt: 0,
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
}],
|
||||
selected_company_id: None,
|
||||
packed_event_collection: None,
|
||||
|
|
@ -692,12 +746,16 @@ mod tests {
|
|||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||
current_cash: 10,
|
||||
debt: 5,
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
},
|
||||
RuntimeCompany {
|
||||
company_id: 2,
|
||||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||
current_cash: 20,
|
||||
debt: 8,
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
},
|
||||
],
|
||||
event_runtime_records: vec![RuntimeEventRecord {
|
||||
|
|
@ -744,12 +802,16 @@ mod tests {
|
|||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 10,
|
||||
debt: 0,
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
},
|
||||
RuntimeCompany {
|
||||
company_id: 2,
|
||||
controller_kind: RuntimeCompanyControllerKind::Ai,
|
||||
current_cash: 20,
|
||||
debt: 2,
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
},
|
||||
],
|
||||
selected_company_id: Some(1),
|
||||
|
|
@ -866,6 +928,179 @@ mod tests {
|
|||
assert!(error.contains("controller_kind"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_active_and_role_targets_exclude_inactive_companies() {
|
||||
let mut state = RuntimeState {
|
||||
companies: vec![
|
||||
RuntimeCompany {
|
||||
company_id: 1,
|
||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 10,
|
||||
debt: 1,
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
},
|
||||
RuntimeCompany {
|
||||
company_id: 2,
|
||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 20,
|
||||
debt: 2,
|
||||
active: false,
|
||||
available_track_laying_capacity: None,
|
||||
},
|
||||
RuntimeCompany {
|
||||
company_id: 3,
|
||||
controller_kind: RuntimeCompanyControllerKind::Ai,
|
||||
current_cash: 30,
|
||||
debt: 3,
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
},
|
||||
],
|
||||
event_runtime_records: vec![
|
||||
RuntimeEventRecord {
|
||||
record_id: 16,
|
||||
trigger_kind: 7,
|
||||
active: true,
|
||||
service_count: 0,
|
||||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
effects: vec![RuntimeEffect::AdjustCompanyCash {
|
||||
target: RuntimeCompanyTarget::AllActive,
|
||||
delta: 5,
|
||||
}],
|
||||
},
|
||||
RuntimeEventRecord {
|
||||
record_id: 17,
|
||||
trigger_kind: 7,
|
||||
active: true,
|
||||
service_count: 0,
|
||||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
effects: vec![RuntimeEffect::AdjustCompanyDebt {
|
||||
target: RuntimeCompanyTarget::HumanCompanies,
|
||||
delta: 4,
|
||||
}],
|
||||
},
|
||||
RuntimeEventRecord {
|
||||
record_id: 18,
|
||||
trigger_kind: 7,
|
||||
active: true,
|
||||
service_count: 0,
|
||||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
effects: vec![RuntimeEffect::AdjustCompanyDebt {
|
||||
target: RuntimeCompanyTarget::AiCompanies,
|
||||
delta: 6,
|
||||
}],
|
||||
},
|
||||
],
|
||||
..state()
|
||||
};
|
||||
|
||||
execute_step_command(
|
||||
&mut state,
|
||||
&StepCommand::ServiceTriggerKind { trigger_kind: 7 },
|
||||
)
|
||||
.expect("active-company filtering should succeed");
|
||||
|
||||
assert_eq!(state.companies[0].current_cash, 15);
|
||||
assert_eq!(state.companies[1].current_cash, 20);
|
||||
assert_eq!(state.companies[2].current_cash, 35);
|
||||
assert_eq!(state.companies[0].debt, 5);
|
||||
assert_eq!(state.companies[1].debt, 2);
|
||||
assert_eq!(state.companies[2].debt, 9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deactivating_selected_company_clears_selection() {
|
||||
let mut state = RuntimeState {
|
||||
companies: vec![RuntimeCompany {
|
||||
company_id: 1,
|
||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 10,
|
||||
debt: 0,
|
||||
active: true,
|
||||
available_track_laying_capacity: Some(8),
|
||||
}],
|
||||
selected_company_id: Some(1),
|
||||
event_runtime_records: vec![RuntimeEventRecord {
|
||||
record_id: 19,
|
||||
trigger_kind: 7,
|
||||
active: true,
|
||||
service_count: 0,
|
||||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
effects: vec![RuntimeEffect::DeactivateCompany {
|
||||
target: RuntimeCompanyTarget::SelectedCompany,
|
||||
}],
|
||||
}],
|
||||
..state()
|
||||
};
|
||||
|
||||
let result = execute_step_command(
|
||||
&mut state,
|
||||
&StepCommand::ServiceTriggerKind { trigger_kind: 7 },
|
||||
)
|
||||
.expect("deactivate company effect should succeed");
|
||||
|
||||
assert!(!state.companies[0].active);
|
||||
assert_eq!(state.selected_company_id, None);
|
||||
assert_eq!(result.service_events[0].mutated_company_ids, vec![1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sets_track_laying_capacity_for_resolved_targets() {
|
||||
let mut state = RuntimeState {
|
||||
companies: vec![
|
||||
RuntimeCompany {
|
||||
company_id: 1,
|
||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 10,
|
||||
debt: 0,
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
},
|
||||
RuntimeCompany {
|
||||
company_id: 2,
|
||||
controller_kind: RuntimeCompanyControllerKind::Ai,
|
||||
current_cash: 20,
|
||||
debt: 0,
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
},
|
||||
],
|
||||
event_runtime_records: vec![RuntimeEventRecord {
|
||||
record_id: 20,
|
||||
trigger_kind: 7,
|
||||
active: true,
|
||||
service_count: 0,
|
||||
marks_collection_dirty: false,
|
||||
one_shot: false,
|
||||
has_fired: false,
|
||||
effects: vec![RuntimeEffect::SetCompanyTrackLayingCapacity {
|
||||
target: RuntimeCompanyTarget::Ids { ids: vec![2] },
|
||||
value: Some(14),
|
||||
}],
|
||||
}],
|
||||
..state()
|
||||
};
|
||||
|
||||
let result = execute_step_command(
|
||||
&mut state,
|
||||
&StepCommand::ServiceTriggerKind { trigger_kind: 7 },
|
||||
)
|
||||
.expect("track capacity effect should succeed");
|
||||
|
||||
assert_eq!(state.companies[0].available_track_laying_capacity, None);
|
||||
assert_eq!(state.companies[1].available_track_laying_capacity, Some(14));
|
||||
assert_eq!(result.service_events[0].mutated_company_ids, vec![2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_condition_true_company_target_without_condition_context() {
|
||||
let mut state = RuntimeState {
|
||||
|
|
@ -941,6 +1176,8 @@ mod tests {
|
|||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||
current_cash: 10,
|
||||
debt: 2,
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
}],
|
||||
event_runtime_records: vec![RuntimeEventRecord {
|
||||
record_id: 30,
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ pub struct RuntimeSummary {
|
|||
pub world_restore_absolute_counter_adjustment_context: Option<String>,
|
||||
pub metadata_count: usize,
|
||||
pub company_count: usize,
|
||||
pub active_company_count: usize,
|
||||
pub packed_event_collection_present: bool,
|
||||
pub packed_event_record_count: usize,
|
||||
pub packed_event_decoded_record_count: usize,
|
||||
|
|
@ -122,6 +123,7 @@ impl RuntimeSummary {
|
|||
.clone(),
|
||||
metadata_count: state.metadata.len(),
|
||||
company_count: state.companies.len(),
|
||||
active_company_count: state.companies.iter().filter(|company| company.active).count(),
|
||||
packed_event_collection_present: state.packed_event_collection.is_some(),
|
||||
packed_event_record_count: state
|
||||
.packed_event_collection
|
||||
|
|
@ -302,7 +304,8 @@ mod tests {
|
|||
use std::collections::BTreeMap;
|
||||
|
||||
use crate::{
|
||||
CalendarPoint, RuntimePackedEventCollectionSummary, RuntimePackedEventRecordSummary,
|
||||
CalendarPoint, RuntimeCompany, RuntimeCompanyControllerKind,
|
||||
RuntimePackedEventCollectionSummary, RuntimePackedEventRecordSummary,
|
||||
RuntimeSaveProfileState, RuntimeServiceState, RuntimeState, RuntimeWorldRestoreState,
|
||||
};
|
||||
|
||||
|
|
@ -399,4 +402,48 @@ mod tests {
|
|||
assert_eq!(summary.packed_event_blocked_missing_company_role_context_count, 0);
|
||||
assert_eq!(summary.packed_event_blocked_missing_condition_context_count, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn counts_active_companies_separately_from_total_companies() {
|
||||
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: 10,
|
||||
debt: 0,
|
||||
active: true,
|
||||
available_track_laying_capacity: None,
|
||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
},
|
||||
RuntimeCompany {
|
||||
company_id: 2,
|
||||
current_cash: 20,
|
||||
debt: 0,
|
||||
active: false,
|
||||
available_track_laying_capacity: Some(7),
|
||||
controller_kind: RuntimeCompanyControllerKind::Ai,
|
||||
},
|
||||
],
|
||||
selected_company_id: None,
|
||||
packed_event_collection: None,
|
||||
event_runtime_records: Vec::new(),
|
||||
candidate_availability: BTreeMap::new(),
|
||||
special_conditions: BTreeMap::new(),
|
||||
service_state: RuntimeServiceState::default(),
|
||||
};
|
||||
|
||||
let summary = RuntimeSummary::from_state(&state);
|
||||
assert_eq!(summary.company_count, 2);
|
||||
assert_eq!(summary.active_company_count, 1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,13 +77,14 @@ The highest-value next passes are now:
|
|||
avoid shell-first implementation bets
|
||||
- keep using overlay imports as the context bridge when selectively executable packed rows still
|
||||
need live company state that save slices do not persist
|
||||
- treat broader real grouped-descriptor recovery as the active packed-event frontier now that
|
||||
descriptor `2` `Company Cash` already parses, summarizes, and executes through the ordinary
|
||||
runtime path when overlay context resolves its symbolic company scope
|
||||
- treat broader real grouped-descriptor recovery as the active packed-event frontier now that the
|
||||
first company-scoped batch already parses, summarizes, and executes through the ordinary runtime
|
||||
path when overlay context resolves its symbolic company scope: descriptor `2` `Company Cash`,
|
||||
descriptor `13` `Deactivate Company`, and descriptor `16` `Company Track Pieces Buildable`
|
||||
- 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
|
||||
- leave condition-relative company scopes explicit and blocked until condition evaluation has
|
||||
grounded runtime semantics
|
||||
grounded runtime semantics, and keep mixed supported/unsupported real rows parity-only
|
||||
- keep in mind that the current local `.gms` corpus still exports with no packed event collection,
|
||||
so real descriptor mapping needs to stay plumbing-first until better captures exist
|
||||
- use `rrt-hook` primarily as optional capture or integration tooling, not as the first execution
|
||||
|
|
|
|||
|
|
@ -29,11 +29,13 @@ Implemented today:
|
|||
symbolic scopes through the ordinary runtime service path while keeping condition-relative
|
||||
company scopes explicitly blocked
|
||||
- real `0x4e9a` grouped rows now carry checked-in descriptor metadata, semantic family/preview
|
||||
summaries, and one recovered executable family: descriptor `2` = `Company Cash`
|
||||
summaries, and three recovered executable company-scoped families: descriptor `2` = `Company Cash`,
|
||||
descriptor `13` = `Deactivate Company`, and descriptor `16` = `Company Track Pieces Buildable`
|
||||
|
||||
That means the next implementation work is breadth, not bootstrap. The recommended next slice is
|
||||
broader real grouped-descriptor coverage beyond `Company Cash`, plus condition-relative execution
|
||||
for the still-blocked symbolic scopes, not another persistence scaffold pass.
|
||||
broader real grouped-descriptor coverage beyond the current company-scoped batch, plus
|
||||
condition-relative execution for the still-blocked symbolic scopes, not another persistence
|
||||
scaffold pass.
|
||||
|
||||
## Why This Boundary
|
||||
|
||||
|
|
@ -375,11 +377,12 @@ Checked-in fixture families already include:
|
|||
## Next Slice
|
||||
|
||||
The recommended next implementation slice is broader real grouped-descriptor coverage on top of the
|
||||
now-stable compact-control, symbolic-target, and first recovered real-family path.
|
||||
now-stable compact-control, symbolic-target, and current company-scoped real-family batch.
|
||||
|
||||
Target behavior:
|
||||
|
||||
- keep descriptor `2` `Company Cash` as the proof that real grouped rows can cross the whole path:
|
||||
- keep descriptors `2` `Company Cash`, `13` `Deactivate Company`, and `16`
|
||||
`Company Track Pieces Buildable` as the proof that real grouped rows can cross the whole path:
|
||||
parse, semantic summary, overlay-backed import, and ordinary trigger execution
|
||||
- recover more real descriptor identities from the checked-in effect table and expose their target
|
||||
masks and conservative semantic previews without guessing unsupported behavior
|
||||
|
|
@ -399,8 +402,11 @@ Public-model expectations for that slice:
|
|||
Fixture work for that slice:
|
||||
|
||||
- preserve the parity-heavy tracked sample as the condition-relative blocked frontier while it now
|
||||
carries recovered `Company Cash` semantics
|
||||
- add overlay-backed captured fixtures whenever a new real descriptor family becomes executable
|
||||
carries recovered `Company Cash` semantics with executable import readiness
|
||||
- keep overlay-backed captured fixtures for the executable company-scoped real families:
|
||||
`Company Cash`, `Deactivate Company`, and `Company Track Pieces Buildable`
|
||||
- keep a mixed real-row overlay fixture to lock the all-or-nothing parity rule for partially
|
||||
supported real records
|
||||
- keep synthetic harness, save-slice, and overlay paths green as the real descriptor surface widens
|
||||
|
||||
Current local constraint:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
{
|
||||
"format_version": 1,
|
||||
"fixture_id": "packed-event-deactivate-company-overlay-fixture",
|
||||
"source": {
|
||||
"kind": "captured-runtime",
|
||||
"description": "Fixture backed by an overlay import document so the real Deactivate Company row executes against selected-company context."
|
||||
},
|
||||
"state_import_path": "packed-event-deactivate-company-overlay.json",
|
||||
"commands": [
|
||||
{
|
||||
"kind": "service_trigger_kind",
|
||||
"trigger_kind": 7
|
||||
}
|
||||
],
|
||||
"expected_summary": {
|
||||
"calendar_projection_source": "base-snapshot-preserved",
|
||||
"calendar_projection_is_placeholder": false,
|
||||
"company_count": 3,
|
||||
"active_company_count": 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": {
|
||||
"selected_company_id": null,
|
||||
"companies": [
|
||||
{
|
||||
"company_id": 1,
|
||||
"active": true
|
||||
},
|
||||
{
|
||||
"company_id": 2,
|
||||
"active": true
|
||||
},
|
||||
{
|
||||
"company_id": 3,
|
||||
"active": false
|
||||
}
|
||||
],
|
||||
"packed_event_collection": {
|
||||
"records": [
|
||||
{
|
||||
"import_outcome": "imported"
|
||||
}
|
||||
]
|
||||
},
|
||||
"event_runtime_records": [
|
||||
{
|
||||
"record_id": 31,
|
||||
"service_count": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"format_version": 1,
|
||||
"import_id": "packed-event-deactivate-company-overlay",
|
||||
"source": {
|
||||
"description": "Overlay import document for the real Deactivate Company descriptor sample.",
|
||||
"notes": []
|
||||
},
|
||||
"base_snapshot_path": "packed-event-symbolic-company-scope-overlay-base-snapshot.json",
|
||||
"save_slice_path": "packed-event-deactivate-company-save-slice.json"
|
||||
}
|
||||
106
fixtures/runtime/packed-event-deactivate-company-save-slice.json
Normal file
106
fixtures/runtime/packed-event-deactivate-company-save-slice.json
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
{
|
||||
"format_version": 1,
|
||||
"save_slice_id": "packed-event-deactivate-company-save-slice",
|
||||
"source": {
|
||||
"description": "Tracked save-slice document with a real packed-event row for Deactivate Company.",
|
||||
"original_save_filename": "captured-deactivate-company.gms",
|
||||
"original_save_sha256": "deactivate-company-sample-sha256",
|
||||
"notes": [
|
||||
"tracked as JSON save-slice document rather than raw .smp",
|
||||
"locks executable import for real descriptor 13"
|
||||
]
|
||||
},
|
||||
"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": 31,
|
||||
"live_record_count": 1,
|
||||
"live_entry_ids": [31],
|
||||
"decoded_record_count": 1,
|
||||
"imported_runtime_record_count": 1,
|
||||
"records": [
|
||||
{
|
||||
"record_index": 0,
|
||||
"live_entry_id": 31,
|
||||
"payload_offset": 29186,
|
||||
"payload_len": 120,
|
||||
"decode_status": "parity_only",
|
||||
"payload_family": "real_packed_v1",
|
||||
"trigger_kind": 7,
|
||||
"one_shot": false,
|
||||
"compact_control": {
|
||||
"mode_byte_0x7ef": 7,
|
||||
"primary_selector_0x7f0": 99,
|
||||
"grouped_mode_0x7f4": 2,
|
||||
"one_shot_header_0x7f5": 0,
|
||||
"modifier_flag_0x7f9": 1,
|
||||
"modifier_flag_0x7fa": 0,
|
||||
"grouped_target_scope_ordinals_0x7fb": [1, 1, 1, 1],
|
||||
"grouped_scope_checkboxes_0x7ff": [1, 0, 0, 0],
|
||||
"summary_toggle_0x800": 1,
|
||||
"grouped_territory_selectors_0x80f": [-1, -1, -1, -1]
|
||||
},
|
||||
"text_bands": [],
|
||||
"standalone_condition_row_count": 0,
|
||||
"standalone_condition_rows": [],
|
||||
"grouped_effect_row_counts": [1, 0, 0, 0],
|
||||
"grouped_effect_rows": [
|
||||
{
|
||||
"group_index": 0,
|
||||
"row_index": 0,
|
||||
"descriptor_id": 13,
|
||||
"descriptor_label": "Deactivate Company",
|
||||
"target_mask_bits": 1,
|
||||
"parameter_family": "company_lifecycle_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 Deactivate Company to TRUE",
|
||||
"locomotive_name": null,
|
||||
"notes": []
|
||||
}
|
||||
],
|
||||
"decoded_actions": [
|
||||
{
|
||||
"kind": "deactivate_company",
|
||||
"target": {
|
||||
"kind": "selected_company"
|
||||
}
|
||||
}
|
||||
],
|
||||
"executable_import_ready": true,
|
||||
"notes": [
|
||||
"decoded from grounded real 0x4e9a row framing"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"notes": [
|
||||
"real descriptor 13 sample"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
{
|
||||
"format_version": 1,
|
||||
"fixture_id": "packed-event-mixed-company-descriptor-overlay-fixture",
|
||||
"source": {
|
||||
"kind": "captured-runtime",
|
||||
"description": "Fixture backed by an overlay import document proving mixed real grouped-row records stay parity-only."
|
||||
},
|
||||
"state_import_path": "packed-event-mixed-company-descriptor-overlay.json",
|
||||
"commands": [
|
||||
{
|
||||
"kind": "service_trigger_kind",
|
||||
"trigger_kind": 7
|
||||
}
|
||||
],
|
||||
"expected_summary": {
|
||||
"calendar_projection_source": "base-snapshot-preserved",
|
||||
"calendar_projection_is_placeholder": false,
|
||||
"company_count": 3,
|
||||
"active_company_count": 3,
|
||||
"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_unmapped_real_descriptor_count": 1,
|
||||
"event_runtime_record_count": 0,
|
||||
"total_event_record_service_count": 0,
|
||||
"total_trigger_dispatch_count": 1
|
||||
},
|
||||
"expected_state_fragment": {
|
||||
"selected_company_id": 3,
|
||||
"companies": [
|
||||
{
|
||||
"company_id": 1,
|
||||
"available_track_laying_capacity": null
|
||||
},
|
||||
{
|
||||
"company_id": 2,
|
||||
"available_track_laying_capacity": null
|
||||
},
|
||||
{
|
||||
"company_id": 3,
|
||||
"available_track_laying_capacity": null
|
||||
}
|
||||
],
|
||||
"packed_event_collection": {
|
||||
"records": [
|
||||
{
|
||||
"import_outcome": "blocked_unmapped_real_descriptor"
|
||||
}
|
||||
]
|
||||
},
|
||||
"event_runtime_records": []
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"format_version": 1,
|
||||
"import_id": "packed-event-mixed-company-descriptor-overlay",
|
||||
"source": {
|
||||
"description": "Overlay import document for a mixed real grouped-row record that should remain parity-only.",
|
||||
"notes": []
|
||||
},
|
||||
"base_snapshot_path": "packed-event-symbolic-company-scope-overlay-base-snapshot.json",
|
||||
"save_slice_path": "packed-event-mixed-company-descriptor-save-slice.json"
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
{
|
||||
"format_version": 1,
|
||||
"save_slice_id": "packed-event-mixed-company-descriptor-save-slice",
|
||||
"source": {
|
||||
"description": "Tracked save-slice document with one supported and one unsupported real company-scoped grouped row.",
|
||||
"original_save_filename": "captured-mixed-company-descriptor.gms",
|
||||
"original_save_sha256": "mixed-company-descriptor-sample-sha256",
|
||||
"notes": [
|
||||
"tracked as JSON save-slice document rather than raw .smp",
|
||||
"locks all-or-nothing import for mixed real grouped-row records"
|
||||
]
|
||||
},
|
||||
"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": 33,
|
||||
"live_record_count": 1,
|
||||
"live_entry_ids": [33],
|
||||
"decoded_record_count": 1,
|
||||
"imported_runtime_record_count": 0,
|
||||
"records": [
|
||||
{
|
||||
"record_index": 0,
|
||||
"live_entry_id": 33,
|
||||
"payload_offset": 29186,
|
||||
"payload_len": 160,
|
||||
"decode_status": "parity_only",
|
||||
"payload_family": "real_packed_v1",
|
||||
"trigger_kind": 7,
|
||||
"one_shot": false,
|
||||
"compact_control": {
|
||||
"mode_byte_0x7ef": 7,
|
||||
"primary_selector_0x7f0": 99,
|
||||
"grouped_mode_0x7f4": 2,
|
||||
"one_shot_header_0x7f5": 0,
|
||||
"modifier_flag_0x7f9": 1,
|
||||
"modifier_flag_0x7fa": 0,
|
||||
"grouped_target_scope_ordinals_0x7fb": [1, 1, 1, 1],
|
||||
"grouped_scope_checkboxes_0x7ff": [1, 1, 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": [1, 1, 0, 0],
|
||||
"grouped_effect_rows": [
|
||||
{
|
||||
"group_index": 0,
|
||||
"row_index": 0,
|
||||
"descriptor_id": 16,
|
||||
"descriptor_label": "Company Track Pieces Buildable",
|
||||
"target_mask_bits": 1,
|
||||
"parameter_family": "company_build_limit_scalar",
|
||||
"opcode": 3,
|
||||
"raw_scalar_value": 18,
|
||||
"value_byte_0x09": 0,
|
||||
"value_dword_0x0d": 0,
|
||||
"value_byte_0x11": 0,
|
||||
"value_byte_0x12": 0,
|
||||
"value_word_0x14": 0,
|
||||
"value_word_0x16": 0,
|
||||
"row_shape": "scalar_assignment",
|
||||
"semantic_family": "scalar_assignment",
|
||||
"semantic_preview": "Set Company Track Pieces Buildable to 18",
|
||||
"locomotive_name": null,
|
||||
"notes": []
|
||||
},
|
||||
{
|
||||
"group_index": 1,
|
||||
"row_index": 0,
|
||||
"descriptor_id": 8,
|
||||
"descriptor_label": "Economic Status",
|
||||
"target_mask_bits": 8,
|
||||
"parameter_family": "whole_game_state_enum",
|
||||
"opcode": 3,
|
||||
"raw_scalar_value": 2,
|
||||
"value_byte_0x09": 0,
|
||||
"value_dword_0x0d": 0,
|
||||
"value_byte_0x11": 0,
|
||||
"value_byte_0x12": 0,
|
||||
"value_word_0x14": 0,
|
||||
"value_word_0x16": 0,
|
||||
"row_shape": "scalar_assignment",
|
||||
"semantic_family": "scalar_assignment",
|
||||
"semantic_preview": "Set Economic Status to 2",
|
||||
"locomotive_name": null,
|
||||
"notes": []
|
||||
}
|
||||
],
|
||||
"decoded_actions": [
|
||||
{
|
||||
"kind": "set_company_track_laying_capacity",
|
||||
"target": {
|
||||
"kind": "selected_company"
|
||||
},
|
||||
"value": 18
|
||||
}
|
||||
],
|
||||
"executable_import_ready": false,
|
||||
"notes": [
|
||||
"decoded from grounded real 0x4e9a row framing"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"notes": [
|
||||
"mixed real descriptor sample"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -162,7 +162,7 @@
|
|||
"value": 7
|
||||
}
|
||||
],
|
||||
"executable_import_ready": false,
|
||||
"executable_import_ready": true,
|
||||
"notes": [
|
||||
"decoded from grounded real 0x4e9a row framing",
|
||||
"grouped descriptor labels and target masks come from the checked-in effect table recovered around 0x006103a0"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
{
|
||||
"format_version": 1,
|
||||
"fixture_id": "packed-event-track-capacity-overlay-fixture",
|
||||
"source": {
|
||||
"kind": "captured-runtime",
|
||||
"description": "Fixture backed by an overlay import document so the real Company Track Pieces Buildable row executes against selected-company context."
|
||||
},
|
||||
"state_import_path": "packed-event-track-capacity-overlay.json",
|
||||
"commands": [
|
||||
{
|
||||
"kind": "service_trigger_kind",
|
||||
"trigger_kind": 7
|
||||
}
|
||||
],
|
||||
"expected_summary": {
|
||||
"calendar_projection_source": "base-snapshot-preserved",
|
||||
"calendar_projection_is_placeholder": false,
|
||||
"company_count": 3,
|
||||
"active_company_count": 3,
|
||||
"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": {
|
||||
"selected_company_id": 3,
|
||||
"companies": [
|
||||
{
|
||||
"company_id": 1,
|
||||
"available_track_laying_capacity": null
|
||||
},
|
||||
{
|
||||
"company_id": 2,
|
||||
"available_track_laying_capacity": null
|
||||
},
|
||||
{
|
||||
"company_id": 3,
|
||||
"available_track_laying_capacity": 18
|
||||
}
|
||||
],
|
||||
"packed_event_collection": {
|
||||
"records": [
|
||||
{
|
||||
"import_outcome": "imported"
|
||||
}
|
||||
]
|
||||
},
|
||||
"event_runtime_records": [
|
||||
{
|
||||
"record_id": 32,
|
||||
"service_count": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
10
fixtures/runtime/packed-event-track-capacity-overlay.json
Normal file
10
fixtures/runtime/packed-event-track-capacity-overlay.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"format_version": 1,
|
||||
"import_id": "packed-event-track-capacity-overlay",
|
||||
"source": {
|
||||
"description": "Overlay import document for the real Company Track Pieces Buildable descriptor sample.",
|
||||
"notes": []
|
||||
},
|
||||
"base_snapshot_path": "packed-event-symbolic-company-scope-overlay-base-snapshot.json",
|
||||
"save_slice_path": "packed-event-track-capacity-save-slice.json"
|
||||
}
|
||||
107
fixtures/runtime/packed-event-track-capacity-save-slice.json
Normal file
107
fixtures/runtime/packed-event-track-capacity-save-slice.json
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
{
|
||||
"format_version": 1,
|
||||
"save_slice_id": "packed-event-track-capacity-save-slice",
|
||||
"source": {
|
||||
"description": "Tracked save-slice document with a real packed-event row for Company Track Pieces Buildable.",
|
||||
"original_save_filename": "captured-track-capacity.gms",
|
||||
"original_save_sha256": "track-capacity-sample-sha256",
|
||||
"notes": [
|
||||
"tracked as JSON save-slice document rather than raw .smp",
|
||||
"locks executable import for real descriptor 16"
|
||||
]
|
||||
},
|
||||
"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": 32,
|
||||
"live_record_count": 1,
|
||||
"live_entry_ids": [32],
|
||||
"decoded_record_count": 1,
|
||||
"imported_runtime_record_count": 1,
|
||||
"records": [
|
||||
{
|
||||
"record_index": 0,
|
||||
"live_entry_id": 32,
|
||||
"payload_offset": 29186,
|
||||
"payload_len": 120,
|
||||
"decode_status": "parity_only",
|
||||
"payload_family": "real_packed_v1",
|
||||
"trigger_kind": 7,
|
||||
"one_shot": false,
|
||||
"compact_control": {
|
||||
"mode_byte_0x7ef": 7,
|
||||
"primary_selector_0x7f0": 99,
|
||||
"grouped_mode_0x7f4": 2,
|
||||
"one_shot_header_0x7f5": 0,
|
||||
"modifier_flag_0x7f9": 1,
|
||||
"modifier_flag_0x7fa": 0,
|
||||
"grouped_target_scope_ordinals_0x7fb": [1, 1, 1, 1],
|
||||
"grouped_scope_checkboxes_0x7ff": [1, 0, 0, 0],
|
||||
"summary_toggle_0x800": 1,
|
||||
"grouped_territory_selectors_0x80f": [-1, -1, -1, -1]
|
||||
},
|
||||
"text_bands": [],
|
||||
"standalone_condition_row_count": 0,
|
||||
"standalone_condition_rows": [],
|
||||
"grouped_effect_row_counts": [1, 0, 0, 0],
|
||||
"grouped_effect_rows": [
|
||||
{
|
||||
"group_index": 0,
|
||||
"row_index": 0,
|
||||
"descriptor_id": 16,
|
||||
"descriptor_label": "Company Track Pieces Buildable",
|
||||
"target_mask_bits": 1,
|
||||
"parameter_family": "company_build_limit_scalar",
|
||||
"opcode": 3,
|
||||
"raw_scalar_value": 18,
|
||||
"value_byte_0x09": 0,
|
||||
"value_dword_0x0d": 0,
|
||||
"value_byte_0x11": 0,
|
||||
"value_byte_0x12": 0,
|
||||
"value_word_0x14": 0,
|
||||
"value_word_0x16": 0,
|
||||
"row_shape": "scalar_assignment",
|
||||
"semantic_family": "scalar_assignment",
|
||||
"semantic_preview": "Set Company Track Pieces Buildable to 18",
|
||||
"locomotive_name": null,
|
||||
"notes": []
|
||||
}
|
||||
],
|
||||
"decoded_actions": [
|
||||
{
|
||||
"kind": "set_company_track_laying_capacity",
|
||||
"target": {
|
||||
"kind": "selected_company"
|
||||
},
|
||||
"value": 18
|
||||
}
|
||||
],
|
||||
"executable_import_ready": true,
|
||||
"notes": [
|
||||
"decoded from grounded real 0x4e9a row framing"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"notes": [
|
||||
"real descriptor 16 sample"
|
||||
]
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue