Recover whole-game packed event descriptors

This commit is contained in:
Jan Petykiewicz 2026-04-15 22:19:09 -07:00
commit a3f9a73766
8 changed files with 627 additions and 16 deletions

View file

@ -27,9 +27,12 @@ train roster and opaque economic-status lane needed for real descriptors `8` `Ec
`Territory - Allow All` now executes too, reinterpreted as company-to-territory access rights `Territory - Allow All` now executes too, reinterpreted as company-to-territory access rights
rather than a territory-owned policy bit. Whole-game ordinary-condition execution now exists too: rather than a territory-owned policy bit. Whole-game ordinary-condition execution now exists too:
special-condition thresholds, candidate-availability thresholds, and economic-status-code special-condition thresholds, candidate-availability thresholds, and economic-status-code
thresholds now gate imported runtime records through the same service path, with explicit unmapped thresholds now gate imported runtime records through the same service path, and checked-in
world-condition and world-descriptor frontier buckets where current checked-in metadata still whole-game descriptor metadata now drives the first real world-side effect batch too:
stops. Shell purchase-flow and selected-profile parity remain out of scope. Mixed special-condition and candidate-availability setters import natively while world-flag rows remain
parity-only until keyed mapping is grounded. Explicit unmapped world-condition and
world-descriptor frontier buckets remain where current checked-in metadata still stops. Shell
purchase-flow and selected-profile parity remain out of scope. Mixed
supported/unsupported real rows still stay parity-only. The PE32 hook remains useful as capture supported/unsupported real rows still stay parity-only. The PE32 hook remains useful as capture
and integration tooling, but it is no longer the main execution milestone. and integration tooling, but it is no longer the main execution milestone.

View file

@ -2739,6 +2739,85 @@ mod tests {
} }
} }
fn real_special_condition_row(
value: i32,
) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary {
crate::SmpLoadedPackedEventGroupedEffectRowSummary {
group_index: 0,
row_index: 0,
descriptor_id: 108,
descriptor_label: Some("Use Wartime Cargos".to_string()),
target_mask_bits: Some(0x08),
parameter_family: Some("special_condition_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 Use Wartime Cargos to {value}")),
locomotive_name: None,
notes: vec![],
}
}
fn real_candidate_availability_row(
value: i32,
) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary {
crate::SmpLoadedPackedEventGroupedEffectRowSummary {
group_index: 0,
row_index: 0,
descriptor_id: 109,
descriptor_label: Some("Turbo Diesel Availability".to_string()),
target_mask_bits: Some(0x08),
parameter_family: Some("candidate_availability_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 Turbo Diesel Availability to {value}")),
locomotive_name: None,
notes: vec![],
}
}
fn real_world_flag_row(enabled: bool) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary {
crate::SmpLoadedPackedEventGroupedEffectRowSummary {
group_index: 0,
row_index: 0,
descriptor_id: 110,
descriptor_label: Some("Disable Stock Buying and Selling".to_string()),
target_mask_bits: Some(0x08),
parameter_family: Some("world_flag_toggle".to_string()),
opcode: 0,
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 Disable Stock Buying and Selling to {}",
if enabled { "TRUE" } else { "FALSE" }
)),
locomotive_name: None,
notes: vec![],
}
}
fn real_confiscate_all_row( fn real_confiscate_all_row(
enabled: bool, enabled: bool,
) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary { ) -> crate::SmpLoadedPackedEventGroupedEffectRowSummary {
@ -5117,6 +5196,245 @@ mod tests {
assert_eq!(import.state.world_restore.economic_status_code, Some(2)); assert_eq!(import.state.world_restore.economic_status_code, Some(2));
} }
#[test]
fn overlays_real_special_condition_descriptor_into_executable_runtime_record() {
let base_state = 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: 21,
live_record_count: 1,
live_entry_ids: vec![21],
decoded_record_count: 1,
imported_runtime_record_count: 0,
records: vec![crate::SmpLoadedPackedEventRecordSummary {
record_index: 0,
live_entry_id: 21,
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(real_compact_control()),
text_bands: packed_text_bands(),
standalone_condition_row_count: 0,
standalone_condition_rows: vec![],
negative_sentinel_scope: None,
grouped_effect_row_counts: vec![1, 0, 0, 0],
grouped_effect_rows: vec![real_special_condition_row(1)],
decoded_conditions: Vec::new(),
decoded_actions: vec![RuntimeEffect::SetSpecialCondition {
label: "Use Wartime Cargos".to_string(),
value: 1,
}],
executable_import_ready: true,
notes: vec![
"decoded from grounded real 0x4e9a row framing".to_string(),
"whole-game descriptor labels and parameter families come from the checked-in effect table".to_string(),
],
}],
}),
notes: vec![],
};
let mut import = project_save_slice_overlay_to_runtime_state_import(
&base_state,
&save_slice,
"real-special-condition-overlay",
None,
)
.expect("overlay import should project");
execute_step_command(
&mut import.state,
&StepCommand::ServiceTriggerKind { trigger_kind: 7 },
)
.expect("real special-condition descriptor should execute");
assert_eq!(
import.state.special_conditions.get("Use Wartime Cargos"),
Some(&1)
);
}
#[test]
fn overlays_real_candidate_availability_descriptor_into_executable_runtime_record() {
let base_state = 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: 22,
live_record_count: 1,
live_entry_ids: vec![22],
decoded_record_count: 1,
imported_runtime_record_count: 0,
records: vec![crate::SmpLoadedPackedEventRecordSummary {
record_index: 0,
live_entry_id: 22,
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(real_compact_control()),
text_bands: packed_text_bands(),
standalone_condition_row_count: 0,
standalone_condition_rows: vec![],
negative_sentinel_scope: None,
grouped_effect_row_counts: vec![1, 0, 0, 0],
grouped_effect_rows: vec![real_candidate_availability_row(1)],
decoded_conditions: Vec::new(),
decoded_actions: vec![RuntimeEffect::SetCandidateAvailability {
name: "Turbo Diesel".to_string(),
value: 1,
}],
executable_import_ready: true,
notes: vec![
"decoded from grounded real 0x4e9a row framing".to_string(),
"whole-game descriptor labels and parameter families come from the checked-in effect table".to_string(),
],
}],
}),
notes: vec![],
};
let mut import = project_save_slice_overlay_to_runtime_state_import(
&base_state,
&save_slice,
"real-candidate-availability-overlay",
None,
)
.expect("overlay import should project");
execute_step_command(
&mut import.state,
&StepCommand::ServiceTriggerKind { trigger_kind: 7 },
)
.expect("real candidate-availability descriptor should execute");
assert_eq!(
import.state.candidate_availability.get("Turbo Diesel"),
Some(&1)
);
}
#[test]
fn leaves_real_world_flag_descriptor_parity_only_until_key_mapping_is_grounded() {
let base_state = 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: 23,
live_record_count: 1,
live_entry_ids: vec![23],
decoded_record_count: 1,
imported_runtime_record_count: 0,
records: vec![crate::SmpLoadedPackedEventRecordSummary {
record_index: 0,
live_entry_id: 23,
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(real_compact_control()),
text_bands: packed_text_bands(),
standalone_condition_row_count: 0,
standalone_condition_rows: vec![],
negative_sentinel_scope: None,
grouped_effect_row_counts: vec![1, 0, 0, 0],
grouped_effect_rows: vec![real_world_flag_row(true)],
decoded_conditions: Vec::new(),
decoded_actions: vec![],
executable_import_ready: false,
notes: vec![
"decoded from grounded real 0x4e9a row framing".to_string(),
"world-flag descriptor identity is checked in, but keyed runtime mapping remains parity-only".to_string(),
],
}],
}),
notes: vec![],
};
let import = project_save_slice_overlay_to_runtime_state_import(
&base_state,
&save_slice,
"real-world-flag-parity-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_world_descriptor")
);
}
#[test] #[test]
fn overlays_real_confiscate_all_descriptor_into_executable_runtime_record() { fn overlays_real_confiscate_all_descriptor_into_executable_runtime_record() {
let base_state = RuntimeState { let base_state = RuntimeState {

View file

@ -127,7 +127,7 @@ struct RealGroupedEffectDescriptorMetadata {
executable_in_runtime: bool, executable_in_runtime: bool,
} }
const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetadata; 8] = [ const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetadata; 11] = [
RealGroupedEffectDescriptorMetadata { RealGroupedEffectDescriptorMetadata {
descriptor_id: 1, descriptor_id: 1,
label: "Player Cash", label: "Player Cash",
@ -156,6 +156,27 @@ const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetad
parameter_family: "whole_game_state_enum", parameter_family: "whole_game_state_enum",
executable_in_runtime: true, executable_in_runtime: true,
}, },
RealGroupedEffectDescriptorMetadata {
descriptor_id: 108,
label: "Use Wartime Cargos",
target_mask_bits: 0x08,
parameter_family: "special_condition_scalar",
executable_in_runtime: true,
},
RealGroupedEffectDescriptorMetadata {
descriptor_id: 109,
label: "Turbo Diesel Availability",
target_mask_bits: 0x08,
parameter_family: "candidate_availability_scalar",
executable_in_runtime: true,
},
RealGroupedEffectDescriptorMetadata {
descriptor_id: 110,
label: "Disable Stock Buying and Selling",
target_mask_bits: 0x08,
parameter_family: "world_flag_toggle",
executable_in_runtime: false,
},
RealGroupedEffectDescriptorMetadata { RealGroupedEffectDescriptorMetadata {
descriptor_id: 9, descriptor_id: 9,
label: "Confiscate All", label: "Confiscate All",
@ -2421,17 +2442,8 @@ fn parse_real_grouped_effect_row_summary(
let value_byte_0x12 = read_u8_at(row_bytes, 0x12)?; let value_byte_0x12 = read_u8_at(row_bytes, 0x12)?;
let value_word_0x14 = read_u16_at(row_bytes, 0x14)?; let value_word_0x14 = read_u16_at(row_bytes, 0x14)?;
let value_word_0x16 = read_u16_at(row_bytes, 0x16)?; let value_word_0x16 = read_u16_at(row_bytes, 0x16)?;
let row_shape = classify_real_grouped_effect_row_shape(
opcode,
raw_scalar_value,
value_byte_0x11,
value_byte_0x12,
value_word_0x14,
value_word_0x16,
)
.to_string();
let descriptor_metadata = real_grouped_effect_descriptor_metadata(descriptor_id); let descriptor_metadata = real_grouped_effect_descriptor_metadata(descriptor_id);
let semantic_family = classify_real_grouped_effect_semantic_family( let mut row_shape = classify_real_grouped_effect_row_shape(
opcode, opcode,
raw_scalar_value, raw_scalar_value,
value_byte_0x11, value_byte_0x11,
@ -2440,6 +2452,28 @@ fn parse_real_grouped_effect_row_summary(
value_word_0x16, value_word_0x16,
) )
.to_string(); .to_string();
let mut semantic_family = classify_real_grouped_effect_semantic_family(
opcode,
raw_scalar_value,
value_byte_0x11,
value_byte_0x12,
value_word_0x14,
value_word_0x16,
)
.to_string();
if descriptor_metadata.is_some_and(|metadata| {
matches!(
metadata.parameter_family,
"special_condition_scalar" | "candidate_availability_scalar"
) && opcode == 3
&& value_byte_0x11 == 0
&& value_byte_0x12 == 0
&& value_word_0x14 == 0
&& value_word_0x16 == 0
}) {
row_shape = "scalar_assignment".to_string();
semantic_family = "scalar_assignment".to_string();
}
let mut notes = Vec::new(); let mut notes = Vec::new();
if locomotive_name.is_some() { if locomotive_name.is_some() {
@ -2606,6 +2640,13 @@ fn build_real_grouped_effect_semantic_preview(
} }
} }
fn runtime_candidate_availability_name(label: &str) -> String {
label
.strip_suffix(" Availability")
.unwrap_or(label)
.to_string()
}
fn decode_real_grouped_effect_actions( fn decode_real_grouped_effect_actions(
grouped_effect_rows: &[SmpLoadedPackedEventGroupedEffectRowSummary], grouped_effect_rows: &[SmpLoadedPackedEventGroupedEffectRowSummary],
compact_control: &SmpLoadedPackedEventCompactControlSummary, compact_control: &SmpLoadedPackedEventCompactControlSummary,
@ -2680,6 +2721,26 @@ fn decode_real_grouped_effect_action(
}); });
} }
if descriptor_metadata.executable_in_runtime
&& descriptor_metadata.descriptor_id == 108
&& row.row_shape == "scalar_assignment"
{
return Some(RuntimeEffect::SetSpecialCondition {
label: descriptor_metadata.label.to_string(),
value: row.raw_scalar_value as u32,
});
}
if descriptor_metadata.executable_in_runtime
&& descriptor_metadata.descriptor_id == 109
&& row.row_shape == "scalar_assignment"
{
return Some(RuntimeEffect::SetCandidateAvailability {
name: runtime_candidate_availability_name(descriptor_metadata.label),
value: row.raw_scalar_value as u32,
});
}
if descriptor_metadata.executable_in_runtime if descriptor_metadata.executable_in_runtime
&& descriptor_metadata.descriptor_id == 9 && descriptor_metadata.descriptor_id == 9
&& row.row_shape == "bool_toggle" && row.row_shape == "bool_toggle"
@ -8251,6 +8312,222 @@ mod tests {
); );
} }
#[test]
fn decodes_real_special_condition_descriptor_from_checked_in_metadata() {
let grouped_row = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
descriptor_id: 108,
opcode: 3,
raw_scalar_value: 1,
value_byte_0x09: 0,
value_dword_0x0d: 0,
value_byte_0x11: 0,
value_byte_0x12: 0,
value_word_0x14: 0,
value_word_0x16: 0,
locomotive_name: None,
});
let group0_rows = vec![grouped_row];
let record_body = build_real_event_record(
[b"World", b"", b"", b"", b"", b""],
Some(RealCompactControlSpec {
mode_byte_0x7ef: 7,
primary_selector_0x7f0: 0,
grouped_mode_0x7f4: 2,
one_shot_header_0x7f5: 0,
modifier_flag_0x7f9: 0,
modifier_flag_0x7fa: 0,
grouped_target_scope_ordinals_0x7fb: [0, 0, 0, 0],
grouped_scope_checkboxes_0x7ff: [1, 0, 0, 0],
summary_toggle_0x800: 1,
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
}),
&[],
[&group0_rows, &[], &[], &[]],
);
let mut bytes = Vec::new();
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_METADATA_TAG.to_le_bytes());
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION.to_le_bytes());
let header_words = [1u32, 4, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
for word in header_words {
bytes.extend_from_slice(&word.to_le_bytes());
}
bytes.extend_from_slice(&[0x00, 0x00]);
bytes.extend_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd]);
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_RECORDS_TAG.to_le_bytes());
bytes.extend_from_slice(&record_body);
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_CLOSE_TAG.to_le_bytes());
let report = inspect_smp_bytes(&bytes);
let summary = report
.event_runtime_collection_summary
.as_ref()
.expect("event runtime collection summary should parse");
assert_eq!(
summary.records[0].grouped_effect_rows[0]
.descriptor_label
.as_deref(),
Some("Use Wartime Cargos")
);
assert_eq!(
summary.records[0].grouped_effect_rows[0]
.parameter_family
.as_deref(),
Some("special_condition_scalar")
);
assert_eq!(
summary.records[0].decoded_actions,
vec![RuntimeEffect::SetSpecialCondition {
label: "Use Wartime Cargos".to_string(),
value: 1,
}]
);
assert!(summary.records[0].executable_import_ready);
}
#[test]
fn decodes_real_candidate_availability_descriptor_from_checked_in_metadata() {
let grouped_row = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
descriptor_id: 109,
opcode: 3,
raw_scalar_value: 1,
value_byte_0x09: 0,
value_dword_0x0d: 0,
value_byte_0x11: 0,
value_byte_0x12: 0,
value_word_0x14: 0,
value_word_0x16: 0,
locomotive_name: None,
});
let group0_rows = vec![grouped_row];
let record_body = build_real_event_record(
[b"World", b"", b"", b"", b"", b""],
Some(RealCompactControlSpec {
mode_byte_0x7ef: 7,
primary_selector_0x7f0: 0,
grouped_mode_0x7f4: 2,
one_shot_header_0x7f5: 0,
modifier_flag_0x7f9: 0,
modifier_flag_0x7fa: 0,
grouped_target_scope_ordinals_0x7fb: [0, 0, 0, 0],
grouped_scope_checkboxes_0x7ff: [1, 0, 0, 0],
summary_toggle_0x800: 1,
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
}),
&[],
[&group0_rows, &[], &[], &[]],
);
let mut bytes = Vec::new();
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_METADATA_TAG.to_le_bytes());
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION.to_le_bytes());
let header_words = [1u32, 4, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
for word in header_words {
bytes.extend_from_slice(&word.to_le_bytes());
}
bytes.extend_from_slice(&[0x00, 0x00]);
bytes.extend_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd]);
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_RECORDS_TAG.to_le_bytes());
bytes.extend_from_slice(&record_body);
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_CLOSE_TAG.to_le_bytes());
let report = inspect_smp_bytes(&bytes);
let summary = report
.event_runtime_collection_summary
.as_ref()
.expect("event runtime collection summary should parse");
assert_eq!(
summary.records[0].grouped_effect_rows[0]
.descriptor_label
.as_deref(),
Some("Turbo Diesel Availability")
);
assert_eq!(
summary.records[0].grouped_effect_rows[0]
.parameter_family
.as_deref(),
Some("candidate_availability_scalar")
);
assert_eq!(
summary.records[0].decoded_actions,
vec![RuntimeEffect::SetCandidateAvailability {
name: "Turbo Diesel".to_string(),
value: 1,
}]
);
assert!(summary.records[0].executable_import_ready);
}
#[test]
fn keeps_real_world_flag_descriptor_parity_only_with_checked_in_metadata() {
let grouped_row = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
descriptor_id: 110,
opcode: 0,
raw_scalar_value: 1,
value_byte_0x09: 0,
value_dword_0x0d: 0,
value_byte_0x11: 0,
value_byte_0x12: 0,
value_word_0x14: 0,
value_word_0x16: 0,
locomotive_name: None,
});
let group0_rows = vec![grouped_row];
let record_body = build_real_event_record(
[b"World", b"", b"", b"", b"", b""],
Some(RealCompactControlSpec {
mode_byte_0x7ef: 7,
primary_selector_0x7f0: 0,
grouped_mode_0x7f4: 2,
one_shot_header_0x7f5: 0,
modifier_flag_0x7f9: 0,
modifier_flag_0x7fa: 0,
grouped_target_scope_ordinals_0x7fb: [0, 0, 0, 0],
grouped_scope_checkboxes_0x7ff: [1, 0, 0, 0],
summary_toggle_0x800: 1,
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
}),
&[],
[&group0_rows, &[], &[], &[]],
);
let mut bytes = Vec::new();
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_METADATA_TAG.to_le_bytes());
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION.to_le_bytes());
let header_words = [1u32, 4, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
for word in header_words {
bytes.extend_from_slice(&word.to_le_bytes());
}
bytes.extend_from_slice(&[0x00, 0x00]);
bytes.extend_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd]);
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_RECORDS_TAG.to_le_bytes());
bytes.extend_from_slice(&record_body);
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_CLOSE_TAG.to_le_bytes());
let report = inspect_smp_bytes(&bytes);
let summary = report
.event_runtime_collection_summary
.as_ref()
.expect("event runtime collection summary should parse");
assert_eq!(
summary.records[0].grouped_effect_rows[0]
.descriptor_label
.as_deref(),
Some("Disable Stock Buying and Selling")
);
assert_eq!(
summary.records[0].grouped_effect_rows[0]
.parameter_family
.as_deref(),
Some("world_flag_toggle")
);
assert!(summary.records[0].decoded_actions.is_empty());
assert!(!summary.records[0].executable_import_ready);
}
#[test] #[test]
fn decodes_negative_sentinel_scope_modifiers_and_territory_marker() { fn decodes_negative_sentinel_scope_modifiers_and_territory_marker() {
for (value, expected) in [ for (value, expected) in [

View file

@ -101,6 +101,10 @@ The highest-value next passes are now:
candidate-availability thresholds, and economic-status-code thresholds now gate imported runtime candidate-availability thresholds, and economic-status-code thresholds now gate imported runtime
records, and the packed-event frontier now reports explicit unmapped world-condition and records, and the packed-event frontier now reports explicit unmapped world-condition and
world-descriptor buckets world-descriptor buckets
- the first real whole-game grouped-descriptor batch is now metadata-driven too: checked-in
descriptor metadata covers special-condition and candidate-availability setters, while the
current world-flag family stays parity-only until keyed flag identity is grounded well enough
for execution
- keep in mind that the current local `.gms` corpus still exports with no packed event collection, - keep in mind that the current local `.gms` corpus still exports with no packed event collection,
so real descriptor mapping needs to stay plumbing-first until better captures exist so real descriptor mapping needs to stay plumbing-first until better captures exist
- use `rrt-hook` primarily as optional capture or integration tooling, not as the first execution - use `rrt-hook` primarily as optional capture or integration tooling, not as the first execution

View file

@ -52,11 +52,17 @@ Implemented today:
through the same service path, and whole-game parity frontiers now report explicit unmapped through the same service path, and whole-game parity frontiers now report explicit unmapped
world-condition and world-descriptor buckets rather than falling back to the generic ordinary world-condition and world-descriptor buckets rather than falling back to the generic ordinary
or descriptor counts or descriptor counts
- checked-in whole-game grouped-descriptor metadata now drives the first real world-side effect
batch too: real special-condition and candidate-availability setter rows now decode and import
through the ordinary runtime path, while world-flag rows remain parity-only until keyed flag
identity is grounded strongly enough for execution
That means the next implementation work is breadth, not bootstrap. The recommended next slice is That means the next implementation work is breadth, not bootstrap. The recommended next slice is
broader real grouped-descriptor and ordinary condition-id coverage beyond the current access, broader real grouped-descriptor and ordinary condition-id coverage beyond the current access,
whole-game, train, player, and numeric-threshold batches, plus richer runtime ownership only where whole-game, train, player, and numeric-threshold batches, with the whole-game frontier now
a later descriptor or condition family needs more than the current event-owned roster. centered on still-unmapped world-flag families and any later state families that need stronger
checked-in descriptor or key recovery. Richer runtime ownership should still be added only where a
later descriptor or condition family needs more than the current event-owned roster.
## Why This Boundary ## Why This Boundary

View file

@ -7,7 +7,8 @@
"original_save_sha256": "world-condition-gated-sample-sha256", "original_save_sha256": "world-condition-gated-sample-sha256",
"notes": [ "notes": [
"tracked as JSON save-slice document rather than raw .smp", "tracked as JSON save-slice document rather than raw .smp",
"proves whole-game ordinary conditions gate imported runtime effects" "proves whole-game ordinary conditions gate imported runtime effects",
"whole-game grouped descriptor ids now line up with the checked-in metadata table"
] ]
}, },
"save_slice": { "save_slice": {
@ -253,7 +254,7 @@
"semantic_preview": "Set Turbo Diesel Availability to 1", "semantic_preview": "Set Turbo Diesel Availability to 1",
"locomotive_name": null, "locomotive_name": null,
"notes": [ "notes": [
"tracked whole-game grouped-effect sample" "checked-in whole-game grouped-effect sample"
] ]
} }
], ],

View file

@ -7,7 +7,8 @@
"original_save_sha256": "world-parity-sample-sha256", "original_save_sha256": "world-parity-sample-sha256",
"notes": [ "notes": [
"tracked as JSON save-slice document rather than raw .smp", "tracked as JSON save-slice document rather than raw .smp",
"keeps one unmapped world descriptor and one unmapped world condition explicit" "keeps one unmapped world descriptor and one unmapped world condition explicit",
"whole-game world-flag descriptor identity is checked in, but keyed runtime mapping remains parity-only"
] ]
}, },
"save_slice": { "save_slice": {
@ -83,7 +84,7 @@
"semantic_preview": "Set Disable Stock Buying and Selling to TRUE", "semantic_preview": "Set Disable Stock Buying and Selling to TRUE",
"locomotive_name": null, "locomotive_name": null,
"notes": [ "notes": [
"recovered whole-game descriptor family without a checked-in executable mapping yet" "checked-in whole-game descriptor family without a grounded executable key mapping yet"
] ]
} }
], ],

View file

@ -7,7 +7,8 @@
"original_save_sha256": "world-special-condition-sample-sha256", "original_save_sha256": "world-special-condition-sample-sha256",
"notes": [ "notes": [
"tracked as JSON save-slice document rather than raw .smp", "tracked as JSON save-slice document rather than raw .smp",
"proves whole-game special-condition effects import through the ordinary runtime path" "proves whole-game special-condition effects import through the ordinary runtime path",
"grouped descriptor identity now comes from checked-in whole-game metadata rather than fixture-only placeholder handling"
] ]
}, },
"save_slice": { "save_slice": {
@ -83,7 +84,7 @@
"semantic_preview": "Set Use Wartime Cargos to 1", "semantic_preview": "Set Use Wartime Cargos to 1",
"locomotive_name": null, "locomotive_name": null,
"notes": [ "notes": [
"tracked world-side descriptor sample" "checked-in whole-game descriptor metadata sample"
] ]
} }
], ],
@ -97,7 +98,7 @@
], ],
"executable_import_ready": true, "executable_import_ready": true,
"notes": [ "notes": [
"tracked whole-game grouped-effect import sample" "checked-in whole-game grouped-effect import sample"
] ]
} }
] ]