Implement real company-scoped event descriptors

This commit is contained in:
Jan Petykiewicz 2026-04-15 12:11:29 -07:00
commit 780e739daa
21 changed files with 1483 additions and 56 deletions

View file

@ -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

View file

@ -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]

View file

@ -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,

View file

@ -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!(

View file

@ -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,17 +931,14 @@ 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();
return "blocked_unsupported_decode".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,

View file

@ -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());
}
}

View file

@ -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]

View file

@ -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
.selected_company_id
.map(|company_id| vec![company_id])
.ok_or_else(|| "target requires selected_company_id context".to_string()),
RuntimeCompanyTarget::SelectedCompany => {
let selected_company_id = state
.selected_company_id
.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,

View file

@ -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);
}
}

View file

@ -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

View file

@ -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:

View file

@ -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
}
]
}
}

View file

@ -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"
}

View 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"
]
}
}

View file

@ -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": []
}
}

View file

@ -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"
}

View file

@ -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"
]
}
}

View file

@ -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"

View file

@ -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
}
]
}
}

View 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"
}

View 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"
]
}
}