Recover real packed event descriptor semantics
This commit is contained in:
parent
f918d0c4f7
commit
eb6c4833af
9 changed files with 719 additions and 120 deletions
12
README.md
12
README.md
|
|
@ -11,12 +11,14 @@ The long-term direction is still a DLL we can inject into the original executabl
|
||||||
individual functions as we build them out. The active implementation milestone is now a headless
|
individual functions as we build them out. The active implementation milestone is now a headless
|
||||||
runtime rehost layer that can execute deterministic world work, compare normalized state, and grow
|
runtime rehost layer that can execute deterministic world work, compare normalized state, and grow
|
||||||
subsystem breadth without depending on the shell or presentation path. The current packed-event
|
subsystem breadth without depending on the shell or presentation path. The current packed-event
|
||||||
frontier is real grouped-descriptor semantic mapping on top of the existing save-slice, snapshot,
|
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
|
overlay-import, compact-control, and symbolic company-target workflows. The runtime already carries
|
||||||
selected-company and controller-role context through overlay imports so synthetic packed records can
|
selected-company and controller-role context through overlay imports, real descriptor `2`
|
||||||
execute `selected_company`, `human_companies`, and `ai_companies` scopes without a parallel packed
|
`Company Cash` now parses and executes through the ordinary runtime path, and synthetic packed
|
||||||
executor, while condition-relative company scopes remain explicitly blocked. The PE32 hook remains
|
records still exercise the same service engine without a parallel packed executor. Condition-
|
||||||
useful as capture and integration tooling, but it is no longer the main execution milestone.
|
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.
|
||||||
|
|
||||||
## Project Docs
|
## Project Docs
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,9 @@ use serde::{Deserialize, Serialize};
|
||||||
use crate::persistence::{load_runtime_snapshot_document, validate_runtime_snapshot_document};
|
use crate::persistence::{load_runtime_snapshot_document, validate_runtime_snapshot_document};
|
||||||
use crate::{
|
use crate::{
|
||||||
CalendarPoint, RuntimeCompanyControllerKind, RuntimeCompanyTarget, RuntimeEffect,
|
CalendarPoint, RuntimeCompanyControllerKind, RuntimeCompanyTarget, RuntimeEffect,
|
||||||
RuntimeEventRecord, RuntimeEventRecordTemplate,
|
RuntimeEventRecord, RuntimeEventRecordTemplate, RuntimePackedEventCollectionSummary,
|
||||||
RuntimePackedEventCompactControlSummary,
|
RuntimePackedEventCompactControlSummary, RuntimePackedEventConditionRowSummary,
|
||||||
RuntimePackedEventConditionRowSummary, RuntimePackedEventGroupedEffectRowSummary,
|
RuntimePackedEventGroupedEffectRowSummary, RuntimePackedEventRecordSummary,
|
||||||
RuntimePackedEventCollectionSummary, RuntimePackedEventRecordSummary,
|
|
||||||
RuntimePackedEventTextBandSummary, RuntimeSaveProfileState, RuntimeServiceState, RuntimeState,
|
RuntimePackedEventTextBandSummary, RuntimeSaveProfileState, RuntimeServiceState, RuntimeState,
|
||||||
RuntimeWorldRestoreState, SmpLoadedPackedEventRecordSummary,
|
RuntimeWorldRestoreState, SmpLoadedPackedEventRecordSummary,
|
||||||
SmpLoadedPackedEventTextBandSummary, SmpLoadedSaveSlice,
|
SmpLoadedPackedEventTextBandSummary, SmpLoadedSaveSlice,
|
||||||
|
|
@ -133,10 +132,9 @@ impl ImportCompanyContext {
|
||||||
.collect(),
|
.collect(),
|
||||||
selected_company_id: state.selected_company_id,
|
selected_company_id: state.selected_company_id,
|
||||||
has_complete_controller_context: !state.companies.is_empty()
|
has_complete_controller_context: !state.companies.is_empty()
|
||||||
&& state
|
&& state.companies.iter().all(|company| {
|
||||||
.companies
|
company.controller_kind != RuntimeCompanyControllerKind::Unknown
|
||||||
.iter()
|
}),
|
||||||
.all(|company| company.controller_kind != RuntimeCompanyControllerKind::Unknown),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -546,7 +544,9 @@ fn project_packed_event_collection(
|
||||||
let mut imported_runtime_records = Vec::new();
|
let mut imported_runtime_records = Vec::new();
|
||||||
let mut imported_record_ids = BTreeSet::new();
|
let mut imported_record_ids = BTreeSet::new();
|
||||||
for record in &summary.records {
|
for record in &summary.records {
|
||||||
if let Some(import_result) = smp_packed_record_to_runtime_event_record(record, company_context) {
|
if let Some(import_result) =
|
||||||
|
smp_packed_record_to_runtime_event_record(record, company_context)
|
||||||
|
{
|
||||||
let runtime_record = import_result?;
|
let runtime_record = import_result?;
|
||||||
imported_record_ids.insert(record.live_entry_id);
|
imported_record_ids.insert(record.live_entry_id);
|
||||||
imported_runtime_records.push(runtime_record);
|
imported_runtime_records.push(runtime_record);
|
||||||
|
|
@ -684,6 +684,9 @@ fn runtime_packed_event_grouped_effect_row_summary_from_smp(
|
||||||
group_index: row.group_index,
|
group_index: row.group_index,
|
||||||
row_index: row.row_index,
|
row_index: row.row_index,
|
||||||
descriptor_id: row.descriptor_id,
|
descriptor_id: row.descriptor_id,
|
||||||
|
descriptor_label: row.descriptor_label.clone(),
|
||||||
|
target_mask_bits: row.target_mask_bits,
|
||||||
|
parameter_family: row.parameter_family.clone(),
|
||||||
opcode: row.opcode,
|
opcode: row.opcode,
|
||||||
raw_scalar_value: row.raw_scalar_value,
|
raw_scalar_value: row.raw_scalar_value,
|
||||||
value_byte_0x09: row.value_byte_0x09,
|
value_byte_0x09: row.value_byte_0x09,
|
||||||
|
|
@ -693,6 +696,8 @@ fn runtime_packed_event_grouped_effect_row_summary_from_smp(
|
||||||
value_word_0x14: row.value_word_0x14,
|
value_word_0x14: row.value_word_0x14,
|
||||||
value_word_0x16: row.value_word_0x16,
|
value_word_0x16: row.value_word_0x16,
|
||||||
row_shape: row.row_shape.clone(),
|
row_shape: row.row_shape.clone(),
|
||||||
|
semantic_family: row.semantic_family.clone(),
|
||||||
|
semantic_preview: row.semantic_preview.clone(),
|
||||||
locomotive_name: row.locomotive_name.clone(),
|
locomotive_name: row.locomotive_name.clone(),
|
||||||
notes: row.notes.clone(),
|
notes: row.notes.clone(),
|
||||||
}
|
}
|
||||||
|
|
@ -702,11 +707,15 @@ fn smp_packed_record_to_runtime_event_record(
|
||||||
record: &SmpLoadedPackedEventRecordSummary,
|
record: &SmpLoadedPackedEventRecordSummary,
|
||||||
company_context: &ImportCompanyContext,
|
company_context: &ImportCompanyContext,
|
||||||
) -> Option<Result<RuntimeEventRecord, String>> {
|
) -> Option<Result<RuntimeEventRecord, String>> {
|
||||||
if record.decode_status == "unsupported_framing" || record.payload_family == "real_packed_v1" {
|
if record.decode_status == "unsupported_framing" {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if record.payload_family == "real_packed_v1" && record.decoded_actions.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let effects = match smp_runtime_effects_to_runtime_effects(&record.decoded_actions, company_context) {
|
let effects =
|
||||||
|
match smp_runtime_effects_to_runtime_effects(&record.decoded_actions, company_context) {
|
||||||
Ok(effects) => effects,
|
Ok(effects) => effects,
|
||||||
Err(_) => return None,
|
Err(_) => return None,
|
||||||
};
|
};
|
||||||
|
|
@ -718,24 +727,9 @@ fn smp_packed_record_to_runtime_event_record(
|
||||||
record.live_entry_id
|
record.live_entry_id
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
let active = record.active.ok_or_else(|| {
|
let active = record.active.unwrap_or(true);
|
||||||
format!(
|
let marks_collection_dirty = record.marks_collection_dirty.unwrap_or(false);
|
||||||
"packed event record {} is missing active flag",
|
let one_shot = record.one_shot.unwrap_or(false);
|
||||||
record.live_entry_id
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
let marks_collection_dirty = record.marks_collection_dirty.ok_or_else(|| {
|
|
||||||
format!(
|
|
||||||
"packed event record {} is missing dirty flag",
|
|
||||||
record.live_entry_id
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
let one_shot = record.one_shot.ok_or_else(|| {
|
|
||||||
format!(
|
|
||||||
"packed event record {} is missing one_shot flag",
|
|
||||||
record.live_entry_id
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
Ok(RuntimeEventRecordTemplate {
|
Ok(RuntimeEventRecordTemplate {
|
||||||
record_id: record.live_entry_id,
|
record_id: record.live_entry_id,
|
||||||
trigger_kind,
|
trigger_kind,
|
||||||
|
|
@ -767,6 +761,16 @@ fn smp_runtime_effect_to_runtime_effect(
|
||||||
key: key.clone(),
|
key: key.clone(),
|
||||||
value: *value,
|
value: *value,
|
||||||
}),
|
}),
|
||||||
|
RuntimeEffect::SetCompanyCash { target, value } => {
|
||||||
|
if company_target_import_blocker(target, company_context).is_none() {
|
||||||
|
Ok(RuntimeEffect::SetCompanyCash {
|
||||||
|
target: target.clone(),
|
||||||
|
value: *value,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(company_target_import_error_message(target, company_context))
|
||||||
|
}
|
||||||
|
}
|
||||||
RuntimeEffect::AdjustCompanyCash { target, delta } => {
|
RuntimeEffect::AdjustCompanyCash { target, delta } => {
|
||||||
if company_target_import_blocker(target, company_context).is_none() {
|
if company_target_import_blocker(target, company_context).is_none() {
|
||||||
Ok(RuntimeEffect::AdjustCompanyCash {
|
Ok(RuntimeEffect::AdjustCompanyCash {
|
||||||
|
|
@ -908,6 +912,13 @@ fn determine_packed_event_import_outcome(
|
||||||
if record.compact_control.is_none() {
|
if record.compact_control.is_none() {
|
||||||
return "blocked_missing_compact_control".to_string();
|
return "blocked_missing_compact_control".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) {
|
if let Some(blocker) = real_record_company_target_import_blocker(record, company_context) {
|
||||||
return company_target_import_outcome(blocker).to_string();
|
return company_target_import_outcome(blocker).to_string();
|
||||||
}
|
}
|
||||||
|
|
@ -934,14 +945,14 @@ fn runtime_effect_company_target_import_blocker(
|
||||||
company_context: &ImportCompanyContext,
|
company_context: &ImportCompanyContext,
|
||||||
) -> Option<CompanyTargetImportBlocker> {
|
) -> Option<CompanyTargetImportBlocker> {
|
||||||
match effect {
|
match effect {
|
||||||
RuntimeEffect::AdjustCompanyCash { target, .. }
|
RuntimeEffect::SetCompanyCash { target, .. }
|
||||||
|
| RuntimeEffect::AdjustCompanyCash { target, .. }
|
||||||
| RuntimeEffect::AdjustCompanyDebt { target, .. } => {
|
| RuntimeEffect::AdjustCompanyDebt { target, .. } => {
|
||||||
company_target_import_blocker(target, company_context)
|
company_target_import_blocker(target, company_context)
|
||||||
}
|
}
|
||||||
RuntimeEffect::AppendEventRecord { record } => record
|
RuntimeEffect::AppendEventRecord { record } => record.effects.iter().find_map(|nested| {
|
||||||
.effects
|
runtime_effect_company_target_import_blocker(nested, company_context)
|
||||||
.iter()
|
}),
|
||||||
.find_map(|nested| runtime_effect_company_target_import_blocker(nested, company_context)),
|
|
||||||
RuntimeEffect::SetWorldFlag { .. }
|
RuntimeEffect::SetWorldFlag { .. }
|
||||||
| RuntimeEffect::SetCandidateAvailability { .. }
|
| RuntimeEffect::SetCandidateAvailability { .. }
|
||||||
| RuntimeEffect::SetSpecialCondition { .. }
|
| RuntimeEffect::SetSpecialCondition { .. }
|
||||||
|
|
@ -998,15 +1009,11 @@ fn real_record_company_target_import_blocker(
|
||||||
fn company_target_import_outcome(blocker: CompanyTargetImportBlocker) -> &'static str {
|
fn company_target_import_outcome(blocker: CompanyTargetImportBlocker) -> &'static str {
|
||||||
match blocker {
|
match blocker {
|
||||||
CompanyTargetImportBlocker::MissingCompanyContext => "blocked_missing_company_context",
|
CompanyTargetImportBlocker::MissingCompanyContext => "blocked_missing_company_context",
|
||||||
CompanyTargetImportBlocker::MissingSelectionContext => {
|
CompanyTargetImportBlocker::MissingSelectionContext => "blocked_missing_selection_context",
|
||||||
"blocked_missing_selection_context"
|
|
||||||
}
|
|
||||||
CompanyTargetImportBlocker::MissingCompanyRoleContext => {
|
CompanyTargetImportBlocker::MissingCompanyRoleContext => {
|
||||||
"blocked_missing_company_role_context"
|
"blocked_missing_company_role_context"
|
||||||
}
|
}
|
||||||
CompanyTargetImportBlocker::MissingConditionContext => {
|
CompanyTargetImportBlocker::MissingConditionContext => "blocked_missing_condition_context",
|
||||||
"blocked_missing_condition_context"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1391,6 +1398,9 @@ mod tests {
|
||||||
group_index: 0,
|
group_index: 0,
|
||||||
row_index: 0,
|
row_index: 0,
|
||||||
descriptor_id: 2,
|
descriptor_id: 2,
|
||||||
|
descriptor_label: Some("Company Cash".to_string()),
|
||||||
|
target_mask_bits: Some(0x01),
|
||||||
|
parameter_family: Some("company_finance_scalar".to_string()),
|
||||||
opcode: 8,
|
opcode: 8,
|
||||||
raw_scalar_value: 7,
|
raw_scalar_value: 7,
|
||||||
value_byte_0x09: 1,
|
value_byte_0x09: 1,
|
||||||
|
|
@ -1400,6 +1410,8 @@ mod tests {
|
||||||
value_word_0x14: 24,
|
value_word_0x14: 24,
|
||||||
value_word_0x16: 36,
|
value_word_0x16: 36,
|
||||||
row_shape: "multivalue_scalar".to_string(),
|
row_shape: "multivalue_scalar".to_string(),
|
||||||
|
semantic_family: Some("multivalue_scalar".to_string()),
|
||||||
|
semantic_preview: Some("Set Company Cash to 7 with aux [2, 3, 24, 36]".to_string()),
|
||||||
locomotive_name: Some("Mikado".to_string()),
|
locomotive_name: Some("Mikado".to_string()),
|
||||||
notes: vec!["grouped effect row carries locomotive-name side string".to_string()],
|
notes: vec!["grouped effect row carries locomotive-name side string".to_string()],
|
||||||
}]
|
}]
|
||||||
|
|
@ -1420,8 +1432,8 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn real_compact_control_without_symbolic_company_scope(
|
fn real_compact_control_without_symbolic_company_scope()
|
||||||
) -> crate::SmpLoadedPackedEventCompactControlSummary {
|
-> crate::SmpLoadedPackedEventCompactControlSummary {
|
||||||
crate::SmpLoadedPackedEventCompactControlSummary {
|
crate::SmpLoadedPackedEventCompactControlSummary {
|
||||||
mode_byte_0x7ef: 6,
|
mode_byte_0x7ef: 6,
|
||||||
primary_selector_0x7f0: 0x63,
|
primary_selector_0x7f0: 0x63,
|
||||||
|
|
@ -2128,11 +2140,8 @@ mod tests {
|
||||||
notes: vec![],
|
notes: vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
let import = project_save_slice_to_runtime_state_import(
|
let import =
|
||||||
&save_slice,
|
project_save_slice_to_runtime_state_import(&save_slice, "symbolic-blockers", None)
|
||||||
"symbolic-blockers",
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.expect("standalone projection should succeed");
|
.expect("standalone projection should succeed");
|
||||||
|
|
||||||
assert!(import.state.event_runtime_records.is_empty());
|
assert!(import.state.event_runtime_records.is_empty());
|
||||||
|
|
@ -2299,7 +2308,10 @@ mod tests {
|
||||||
standalone_condition_rows: real_condition_rows(),
|
standalone_condition_rows: real_condition_rows(),
|
||||||
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||||
grouped_effect_rows: real_grouped_rows(),
|
grouped_effect_rows: real_grouped_rows(),
|
||||||
decoded_actions: vec![],
|
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
||||||
|
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||||
|
value: 7,
|
||||||
|
}],
|
||||||
executable_import_ready: false,
|
executable_import_ready: false,
|
||||||
notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()],
|
notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()],
|
||||||
}],
|
}],
|
||||||
|
|
@ -2342,7 +2354,8 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn leaves_real_records_with_condition_relative_company_scope_blocked_missing_condition_context() {
|
fn leaves_real_records_with_condition_relative_company_scope_blocked_missing_condition_context()
|
||||||
|
{
|
||||||
let save_slice = SmpLoadedSaveSlice {
|
let save_slice = SmpLoadedSaveSlice {
|
||||||
file_extension_hint: Some("gms".to_string()),
|
file_extension_hint: Some("gms".to_string()),
|
||||||
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
|
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
|
||||||
|
|
@ -2490,6 +2503,150 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn overlays_real_company_cash_descriptor_into_executable_runtime_record() {
|
||||||
|
let base_state = RuntimeState {
|
||||||
|
calendar: CalendarPoint {
|
||||||
|
year: 1845,
|
||||||
|
month_slot: 2,
|
||||||
|
phase_slot: 1,
|
||||||
|
tick_slot: 3,
|
||||||
|
},
|
||||||
|
world_flags: BTreeMap::new(),
|
||||||
|
save_profile: RuntimeSaveProfileState::default(),
|
||||||
|
world_restore: RuntimeWorldRestoreState::default(),
|
||||||
|
metadata: BTreeMap::new(),
|
||||||
|
companies: vec![crate::RuntimeCompany {
|
||||||
|
company_id: 42,
|
||||||
|
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||||
|
current_cash: 500,
|
||||||
|
debt: 20,
|
||||||
|
}],
|
||||||
|
selected_company_id: Some(42),
|
||||||
|
packed_event_collection: None,
|
||||||
|
event_runtime_records: Vec::new(),
|
||||||
|
candidate_availability: BTreeMap::new(),
|
||||||
|
special_conditions: BTreeMap::new(),
|
||||||
|
service_state: RuntimeServiceState::default(),
|
||||||
|
};
|
||||||
|
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: 9,
|
||||||
|
live_record_count: 1,
|
||||||
|
live_entry_ids: vec![9],
|
||||||
|
decoded_record_count: 1,
|
||||||
|
imported_runtime_record_count: 0,
|
||||||
|
records: vec![crate::SmpLoadedPackedEventRecordSummary {
|
||||||
|
record_index: 0,
|
||||||
|
live_entry_id: 9,
|
||||||
|
payload_offset: Some(0x7202),
|
||||||
|
payload_len: Some(133),
|
||||||
|
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![crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||||
|
group_index: 0,
|
||||||
|
row_index: 0,
|
||||||
|
descriptor_id: 2,
|
||||||
|
descriptor_label: Some("Company Cash".to_string()),
|
||||||
|
target_mask_bits: Some(0x01),
|
||||||
|
parameter_family: Some("company_finance_scalar".to_string()),
|
||||||
|
opcode: 8,
|
||||||
|
raw_scalar_value: 250,
|
||||||
|
value_byte_0x09: 1,
|
||||||
|
value_dword_0x0d: 12,
|
||||||
|
value_byte_0x11: 2,
|
||||||
|
value_byte_0x12: 3,
|
||||||
|
value_word_0x14: 24,
|
||||||
|
value_word_0x16: 36,
|
||||||
|
row_shape: "multivalue_scalar".to_string(),
|
||||||
|
semantic_family: Some("multivalue_scalar".to_string()),
|
||||||
|
semantic_preview: Some(
|
||||||
|
"Set Company Cash to 250 with aux [2, 3, 24, 36]".to_string(),
|
||||||
|
),
|
||||||
|
locomotive_name: Some("Mikado".to_string()),
|
||||||
|
notes: vec![
|
||||||
|
"grouped effect row carries locomotive-name side string".to_string(),
|
||||||
|
],
|
||||||
|
}],
|
||||||
|
decoded_actions: vec![RuntimeEffect::SetCompanyCash {
|
||||||
|
target: RuntimeCompanyTarget::SelectedCompany,
|
||||||
|
value: 250,
|
||||||
|
}],
|
||||||
|
executable_import_ready: false,
|
||||||
|
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(),
|
||||||
|
],
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
notes: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut import = project_save_slice_overlay_to_runtime_state_import(
|
||||||
|
&base_state,
|
||||||
|
&save_slice,
|
||||||
|
"real-company-cash-overlay",
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.expect("overlay import should project");
|
||||||
|
|
||||||
|
assert_eq!(import.state.event_runtime_records.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
import
|
||||||
|
.state
|
||||||
|
.packed_event_collection
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|summary| summary.records[0].import_outcome.as_deref()),
|
||||||
|
Some("imported")
|
||||||
|
);
|
||||||
|
|
||||||
|
execute_step_command(
|
||||||
|
&mut import.state,
|
||||||
|
&StepCommand::ServiceTriggerKind { trigger_kind: 7 },
|
||||||
|
)
|
||||||
|
.expect("real company-cash descriptor should execute through the normal trigger path");
|
||||||
|
|
||||||
|
assert_eq!(import.state.companies[0].current_cash, 250);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn overlays_save_slice_events_onto_base_company_context() {
|
fn overlays_save_slice_events_onto_base_company_context() {
|
||||||
let base_state = RuntimeState {
|
let base_state = RuntimeState {
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,10 @@ pub enum RuntimeEffect {
|
||||||
key: String,
|
key: String,
|
||||||
value: bool,
|
value: bool,
|
||||||
},
|
},
|
||||||
|
SetCompanyCash {
|
||||||
|
target: RuntimeCompanyTarget,
|
||||||
|
value: i64,
|
||||||
|
},
|
||||||
AdjustCompanyCash {
|
AdjustCompanyCash {
|
||||||
target: RuntimeCompanyTarget,
|
target: RuntimeCompanyTarget,
|
||||||
delta: i64,
|
delta: i64,
|
||||||
|
|
@ -206,6 +210,12 @@ pub struct RuntimePackedEventGroupedEffectRowSummary {
|
||||||
pub group_index: usize,
|
pub group_index: usize,
|
||||||
pub row_index: usize,
|
pub row_index: usize,
|
||||||
pub descriptor_id: u32,
|
pub descriptor_id: u32,
|
||||||
|
#[serde(default)]
|
||||||
|
pub descriptor_label: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub target_mask_bits: Option<u8>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub parameter_family: Option<String>,
|
||||||
pub opcode: u8,
|
pub opcode: u8,
|
||||||
pub raw_scalar_value: i32,
|
pub raw_scalar_value: i32,
|
||||||
pub value_byte_0x09: u8,
|
pub value_byte_0x09: u8,
|
||||||
|
|
@ -216,6 +226,10 @@ pub struct RuntimePackedEventGroupedEffectRowSummary {
|
||||||
pub value_word_0x16: u16,
|
pub value_word_0x16: u16,
|
||||||
pub row_shape: String,
|
pub row_shape: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub semantic_family: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub semantic_preview: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
pub locomotive_name: Option<String>,
|
pub locomotive_name: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub notes: Vec<String>,
|
pub notes: Vec<String>,
|
||||||
|
|
@ -492,7 +506,8 @@ impl RuntimeState {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if record.payload_family == "real_packed_v1"
|
if record.payload_family == "real_packed_v1"
|
||||||
&& record.standalone_condition_rows.len() != record.standalone_condition_row_count
|
&& record.standalone_condition_rows.len()
|
||||||
|
!= record.standalone_condition_row_count
|
||||||
{
|
{
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"packed_event_collection.records[{record_index}].standalone_condition_rows must match standalone_condition_row_count"
|
"packed_event_collection.records[{record_index}].standalone_condition_rows must match standalone_condition_row_count"
|
||||||
|
|
@ -653,7 +668,8 @@ fn validate_runtime_effect(
|
||||||
return Err("key must not be empty".to_string());
|
return Err("key must not be empty".to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RuntimeEffect::AdjustCompanyCash { target, .. }
|
RuntimeEffect::SetCompanyCash { target, .. }
|
||||||
|
| RuntimeEffect::AdjustCompanyCash { target, .. }
|
||||||
| RuntimeEffect::AdjustCompanyDebt { target, .. } => {
|
| RuntimeEffect::AdjustCompanyDebt { target, .. } => {
|
||||||
validate_company_target(target, valid_company_ids)?;
|
validate_company_target(target, valid_company_ids)?;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,74 @@ const SHARED_SIGNATURE_WORDS_1_TO_7: [u32; 7] = [
|
||||||
0x00002ee0, 0x00040001, 0x00028000, 0x00010000, 0x00000771, 0x00000771, 0x00000771,
|
0x00002ee0, 0x00040001, 0x00028000, 0x00010000, 0x00000771, 0x00000771, 0x00000771,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
struct RealGroupedEffectDescriptorMetadata {
|
||||||
|
descriptor_id: u32,
|
||||||
|
label: &'static str,
|
||||||
|
target_mask_bits: u8,
|
||||||
|
parameter_family: &'static str,
|
||||||
|
executable_in_runtime: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetadata; 8] = [
|
||||||
|
RealGroupedEffectDescriptorMetadata {
|
||||||
|
descriptor_id: 1,
|
||||||
|
label: "Player Cash",
|
||||||
|
target_mask_bits: 0x02,
|
||||||
|
parameter_family: "player_finance_scalar",
|
||||||
|
executable_in_runtime: false,
|
||||||
|
},
|
||||||
|
RealGroupedEffectDescriptorMetadata {
|
||||||
|
descriptor_id: 2,
|
||||||
|
label: "Company Cash",
|
||||||
|
target_mask_bits: 0x01,
|
||||||
|
parameter_family: "company_finance_scalar",
|
||||||
|
executable_in_runtime: true,
|
||||||
|
},
|
||||||
|
RealGroupedEffectDescriptorMetadata {
|
||||||
|
descriptor_id: 3,
|
||||||
|
label: "Territory - Allow All",
|
||||||
|
target_mask_bits: 0x05,
|
||||||
|
parameter_family: "territory_access_toggle",
|
||||||
|
executable_in_runtime: false,
|
||||||
|
},
|
||||||
|
RealGroupedEffectDescriptorMetadata {
|
||||||
|
descriptor_id: 8,
|
||||||
|
label: "Economic Status",
|
||||||
|
target_mask_bits: 0x08,
|
||||||
|
parameter_family: "whole_game_state_enum",
|
||||||
|
executable_in_runtime: false,
|
||||||
|
},
|
||||||
|
RealGroupedEffectDescriptorMetadata {
|
||||||
|
descriptor_id: 9,
|
||||||
|
label: "Confiscate All",
|
||||||
|
target_mask_bits: 0x01,
|
||||||
|
parameter_family: "company_confiscation_variant",
|
||||||
|
executable_in_runtime: false,
|
||||||
|
},
|
||||||
|
RealGroupedEffectDescriptorMetadata {
|
||||||
|
descriptor_id: 13,
|
||||||
|
label: "Deactivate Company",
|
||||||
|
target_mask_bits: 0x01,
|
||||||
|
parameter_family: "company_lifecycle_toggle",
|
||||||
|
executable_in_runtime: false,
|
||||||
|
},
|
||||||
|
RealGroupedEffectDescriptorMetadata {
|
||||||
|
descriptor_id: 15,
|
||||||
|
label: "Retire Train",
|
||||||
|
target_mask_bits: 0x0d,
|
||||||
|
parameter_family: "company_or_territory_asset_toggle",
|
||||||
|
executable_in_runtime: false,
|
||||||
|
},
|
||||||
|
RealGroupedEffectDescriptorMetadata {
|
||||||
|
descriptor_id: 16,
|
||||||
|
label: "Company Track Pieces Buildable",
|
||||||
|
target_mask_bits: 0x01,
|
||||||
|
parameter_family: "company_build_limit_scalar",
|
||||||
|
executable_in_runtime: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
struct KnownSpecialConditionDefinition {
|
struct KnownSpecialConditionDefinition {
|
||||||
slot_index: u8,
|
slot_index: u8,
|
||||||
|
|
@ -1295,6 +1363,12 @@ pub struct SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||||
pub group_index: usize,
|
pub group_index: usize,
|
||||||
pub row_index: usize,
|
pub row_index: usize,
|
||||||
pub descriptor_id: u32,
|
pub descriptor_id: u32,
|
||||||
|
#[serde(default)]
|
||||||
|
pub descriptor_label: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub target_mask_bits: Option<u8>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub parameter_family: Option<String>,
|
||||||
pub opcode: u8,
|
pub opcode: u8,
|
||||||
pub raw_scalar_value: i32,
|
pub raw_scalar_value: i32,
|
||||||
pub value_byte_0x09: u8,
|
pub value_byte_0x09: u8,
|
||||||
|
|
@ -1305,6 +1379,10 @@ pub struct SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||||
pub value_word_0x16: u16,
|
pub value_word_0x16: u16,
|
||||||
pub row_shape: String,
|
pub row_shape: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub semantic_family: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub semantic_preview: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
pub locomotive_name: Option<String>,
|
pub locomotive_name: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub notes: Vec<String>,
|
pub notes: Vec<String>,
|
||||||
|
|
@ -1852,8 +1930,7 @@ fn parse_real_event_runtime_record_summary(
|
||||||
let row_bytes =
|
let row_bytes =
|
||||||
record_body.get(cursor..cursor + PACKED_EVENT_REAL_GROUPED_EFFECT_ROW_LEN)?;
|
record_body.get(cursor..cursor + PACKED_EVENT_REAL_GROUPED_EFFECT_ROW_LEN)?;
|
||||||
cursor += PACKED_EVENT_REAL_GROUPED_EFFECT_ROW_LEN;
|
cursor += PACKED_EVENT_REAL_GROUPED_EFFECT_ROW_LEN;
|
||||||
let locomotive_name =
|
let locomotive_name = parse_optional_u16_len_prefixed_string(record_body, &mut cursor)?;
|
||||||
parse_optional_u16_len_prefixed_string(record_body, &mut cursor)?;
|
|
||||||
grouped_effect_rows.push(parse_real_grouped_effect_row_summary(
|
grouped_effect_rows.push(parse_real_grouped_effect_row_summary(
|
||||||
row_bytes,
|
row_bytes,
|
||||||
group_index,
|
group_index,
|
||||||
|
|
@ -1863,6 +1940,14 @@ fn parse_real_event_runtime_record_summary(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let decoded_actions = compact_control
|
||||||
|
.as_ref()
|
||||||
|
.map(|control| decode_real_grouped_effect_actions(&grouped_effect_rows, control))
|
||||||
|
.unwrap_or_default();
|
||||||
|
let executable_import_ready = !decoded_actions.is_empty()
|
||||||
|
&& decoded_actions
|
||||||
|
.iter()
|
||||||
|
.all(runtime_effect_supported_for_save_import);
|
||||||
let consumed_len = cursor;
|
let consumed_len = cursor;
|
||||||
Some((
|
Some((
|
||||||
SmpLoadedPackedEventRecordSummary {
|
SmpLoadedPackedEventRecordSummary {
|
||||||
|
|
@ -1884,9 +1969,12 @@ fn parse_real_event_runtime_record_summary(
|
||||||
standalone_condition_rows,
|
standalone_condition_rows,
|
||||||
grouped_effect_row_counts,
|
grouped_effect_row_counts,
|
||||||
grouped_effect_rows,
|
grouped_effect_rows,
|
||||||
decoded_actions: Vec::new(),
|
decoded_actions,
|
||||||
executable_import_ready: false,
|
executable_import_ready,
|
||||||
notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()],
|
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(),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
consumed_len,
|
consumed_len,
|
||||||
))
|
))
|
||||||
|
|
@ -1931,8 +2019,7 @@ fn parse_optional_real_compact_control_summary(
|
||||||
let summary_toggle_0x800 = read_u8_at(bytes, local)?;
|
let summary_toggle_0x800 = read_u8_at(bytes, local)?;
|
||||||
local += 1;
|
local += 1;
|
||||||
|
|
||||||
let mut grouped_territory_selectors_0x80f =
|
let mut grouped_territory_selectors_0x80f = Vec::with_capacity(PACKED_EVENT_REAL_GROUP_COUNT);
|
||||||
Vec::with_capacity(PACKED_EVENT_REAL_GROUP_COUNT);
|
|
||||||
for _ in 0..PACKED_EVENT_REAL_GROUP_COUNT {
|
for _ in 0..PACKED_EVENT_REAL_GROUP_COUNT {
|
||||||
grouped_territory_selectors_0x80f.push(read_i32_at(bytes, local)?);
|
grouped_territory_selectors_0x80f.push(read_i32_at(bytes, local)?);
|
||||||
local += 4;
|
local += 4;
|
||||||
|
|
@ -1978,7 +2065,9 @@ fn parse_real_condition_row_summary(
|
||||||
row_index,
|
row_index,
|
||||||
raw_condition_id,
|
raw_condition_id,
|
||||||
subtype,
|
subtype,
|
||||||
flag_bytes: row_bytes.get(5..PACKED_EVENT_REAL_CONDITION_ROW_LEN)?.to_vec(),
|
flag_bytes: row_bytes
|
||||||
|
.get(5..PACKED_EVENT_REAL_CONDITION_ROW_LEN)?
|
||||||
|
.to_vec(),
|
||||||
candidate_name,
|
candidate_name,
|
||||||
notes,
|
notes,
|
||||||
})
|
})
|
||||||
|
|
@ -2008,16 +2097,32 @@ fn parse_real_grouped_effect_row_summary(
|
||||||
value_word_0x16,
|
value_word_0x16,
|
||||||
)
|
)
|
||||||
.to_string();
|
.to_string();
|
||||||
|
let descriptor_metadata = real_grouped_effect_descriptor_metadata(descriptor_id);
|
||||||
|
let 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();
|
||||||
|
|
||||||
let mut notes = Vec::new();
|
let mut notes = Vec::new();
|
||||||
if locomotive_name.is_some() {
|
if locomotive_name.is_some() {
|
||||||
notes.push("grouped effect row carries locomotive-name side string".to_string());
|
notes.push("grouped effect row carries locomotive-name side string".to_string());
|
||||||
}
|
}
|
||||||
|
if descriptor_metadata.is_none() {
|
||||||
|
notes.push("descriptor id not yet recovered in the checked-in effect table".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
Some(SmpLoadedPackedEventGroupedEffectRowSummary {
|
Some(SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||||
group_index,
|
group_index,
|
||||||
row_index,
|
row_index,
|
||||||
descriptor_id,
|
descriptor_id,
|
||||||
|
descriptor_label: descriptor_metadata.map(|metadata| metadata.label.to_string()),
|
||||||
|
target_mask_bits: descriptor_metadata.map(|metadata| metadata.target_mask_bits),
|
||||||
|
parameter_family: descriptor_metadata.map(|metadata| metadata.parameter_family.to_string()),
|
||||||
opcode,
|
opcode,
|
||||||
raw_scalar_value,
|
raw_scalar_value,
|
||||||
value_byte_0x09,
|
value_byte_0x09,
|
||||||
|
|
@ -2027,11 +2132,51 @@ fn parse_real_grouped_effect_row_summary(
|
||||||
value_word_0x14,
|
value_word_0x14,
|
||||||
value_word_0x16,
|
value_word_0x16,
|
||||||
row_shape,
|
row_shape,
|
||||||
|
semantic_family: Some(semantic_family.clone()),
|
||||||
|
semantic_preview: Some(build_real_grouped_effect_semantic_preview(
|
||||||
|
descriptor_metadata.map(|metadata| metadata.label),
|
||||||
|
&semantic_family,
|
||||||
|
raw_scalar_value,
|
||||||
|
value_byte_0x11,
|
||||||
|
value_byte_0x12,
|
||||||
|
value_word_0x14,
|
||||||
|
value_word_0x16,
|
||||||
|
)),
|
||||||
locomotive_name,
|
locomotive_name,
|
||||||
notes,
|
notes,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn real_grouped_effect_descriptor_metadata(
|
||||||
|
descriptor_id: u32,
|
||||||
|
) -> Option<RealGroupedEffectDescriptorMetadata> {
|
||||||
|
REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.find(|metadata| metadata.descriptor_id == descriptor_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn classify_real_grouped_effect_semantic_family(
|
||||||
|
opcode: u8,
|
||||||
|
raw_scalar_value: i32,
|
||||||
|
value_byte_0x11: u8,
|
||||||
|
value_byte_0x12: u8,
|
||||||
|
value_word_0x14: u16,
|
||||||
|
value_word_0x16: u16,
|
||||||
|
) -> &'static str {
|
||||||
|
if opcode == 8 {
|
||||||
|
return "multivalue_scalar";
|
||||||
|
}
|
||||||
|
if value_byte_0x11 != 0 || value_byte_0x12 != 0 || value_word_0x14 != 0 || value_word_0x16 != 0
|
||||||
|
{
|
||||||
|
return "timed_duration";
|
||||||
|
}
|
||||||
|
if raw_scalar_value == 0 || raw_scalar_value == 1 {
|
||||||
|
return "bool_toggle";
|
||||||
|
}
|
||||||
|
"scalar_assignment"
|
||||||
|
}
|
||||||
|
|
||||||
fn classify_real_grouped_effect_row_shape(
|
fn classify_real_grouped_effect_row_shape(
|
||||||
opcode: u8,
|
opcode: u8,
|
||||||
raw_scalar_value: i32,
|
raw_scalar_value: i32,
|
||||||
|
|
@ -2050,7 +2195,77 @@ fn classify_real_grouped_effect_row_shape(
|
||||||
if raw_scalar_value == 0 || raw_scalar_value == 1 {
|
if raw_scalar_value == 0 || raw_scalar_value == 1 {
|
||||||
return "bool_toggle";
|
return "bool_toggle";
|
||||||
}
|
}
|
||||||
"raw_other"
|
"scalar_assignment"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_real_grouped_effect_semantic_preview(
|
||||||
|
descriptor_label: Option<&str>,
|
||||||
|
semantic_family: &str,
|
||||||
|
raw_scalar_value: i32,
|
||||||
|
value_byte_0x11: u8,
|
||||||
|
value_byte_0x12: u8,
|
||||||
|
value_word_0x14: u16,
|
||||||
|
value_word_0x16: u16,
|
||||||
|
) -> String {
|
||||||
|
let label = descriptor_label.unwrap_or("descriptor");
|
||||||
|
match semantic_family {
|
||||||
|
"bool_toggle" => {
|
||||||
|
let state = if raw_scalar_value == 0 {
|
||||||
|
"FALSE"
|
||||||
|
} else {
|
||||||
|
"TRUE"
|
||||||
|
};
|
||||||
|
format!("Set {label} to {state}")
|
||||||
|
}
|
||||||
|
"timed_duration" => format!(
|
||||||
|
"Set {label} to {raw_scalar_value} for {value_word_0x14} years {value_word_0x16} months"
|
||||||
|
),
|
||||||
|
"multivalue_scalar" => format!(
|
||||||
|
"Set {label} to {raw_scalar_value} with aux [{value_byte_0x11}, {value_byte_0x12}, {value_word_0x14}, {value_word_0x16}]"
|
||||||
|
),
|
||||||
|
_ => format!("Set {label} to {raw_scalar_value}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_real_grouped_effect_actions(
|
||||||
|
grouped_effect_rows: &[SmpLoadedPackedEventGroupedEffectRowSummary],
|
||||||
|
compact_control: &SmpLoadedPackedEventCompactControlSummary,
|
||||||
|
) -> Vec<RuntimeEffect> {
|
||||||
|
grouped_effect_rows
|
||||||
|
.iter()
|
||||||
|
.filter_map(|row| decode_real_grouped_effect_action(row, compact_control))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_real_grouped_effect_action(
|
||||||
|
row: &SmpLoadedPackedEventGroupedEffectRowSummary,
|
||||||
|
compact_control: &SmpLoadedPackedEventCompactControlSummary,
|
||||||
|
) -> Option<RuntimeEffect> {
|
||||||
|
let descriptor_metadata = real_grouped_effect_descriptor_metadata(row.descriptor_id)?;
|
||||||
|
let target_scope_ordinal = compact_control
|
||||||
|
.grouped_target_scope_ordinals_0x7fb
|
||||||
|
.get(row.group_index)
|
||||||
|
.copied()?;
|
||||||
|
let target = match target_scope_ordinal {
|
||||||
|
0 => RuntimeCompanyTarget::ConditionTrueCompany,
|
||||||
|
1 => RuntimeCompanyTarget::SelectedCompany,
|
||||||
|
2 => RuntimeCompanyTarget::HumanCompanies,
|
||||||
|
3 => RuntimeCompanyTarget::AiCompanies,
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if descriptor_metadata.executable_in_runtime
|
||||||
|
&& descriptor_metadata.descriptor_id == 2
|
||||||
|
&& row.opcode == 8
|
||||||
|
&& row.row_shape == "multivalue_scalar"
|
||||||
|
{
|
||||||
|
return Some(RuntimeEffect::SetCompanyCash {
|
||||||
|
target,
|
||||||
|
value: i64::from(row.raw_scalar_value),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_synthetic_packed_event_action(bytes: &[u8], cursor: &mut usize) -> Option<RuntimeEffect> {
|
fn parse_synthetic_packed_event_action(bytes: &[u8], cursor: &mut usize) -> Option<RuntimeEffect> {
|
||||||
|
|
@ -2183,7 +2398,10 @@ fn parse_len_prefixed_string(bytes: &[u8], cursor: &mut usize) -> Option<String>
|
||||||
Some(String::from_utf8_lossy(text_bytes).into_owned())
|
Some(String::from_utf8_lossy(text_bytes).into_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_optional_u16_len_prefixed_string(bytes: &[u8], cursor: &mut usize) -> Option<Option<String>> {
|
fn parse_optional_u16_len_prefixed_string(
|
||||||
|
bytes: &[u8],
|
||||||
|
cursor: &mut usize,
|
||||||
|
) -> Option<Option<String>> {
|
||||||
let len = usize::from(read_u16_at(bytes, *cursor)?);
|
let len = usize::from(read_u16_at(bytes, *cursor)?);
|
||||||
*cursor += 2;
|
*cursor += 2;
|
||||||
if len == 0 {
|
if len == 0 {
|
||||||
|
|
@ -2202,7 +2420,8 @@ fn runtime_effect_supported_for_save_import(effect: &RuntimeEffect) -> bool {
|
||||||
| RuntimeEffect::ActivateEventRecord { .. }
|
| RuntimeEffect::ActivateEventRecord { .. }
|
||||||
| RuntimeEffect::DeactivateEventRecord { .. }
|
| RuntimeEffect::DeactivateEventRecord { .. }
|
||||||
| RuntimeEffect::RemoveEventRecord { .. } => true,
|
| RuntimeEffect::RemoveEventRecord { .. } => true,
|
||||||
RuntimeEffect::AdjustCompanyCash { target, .. }
|
RuntimeEffect::SetCompanyCash { target, .. }
|
||||||
|
| RuntimeEffect::AdjustCompanyCash { target, .. }
|
||||||
| RuntimeEffect::AdjustCompanyDebt { target, .. } => {
|
| RuntimeEffect::AdjustCompanyDebt { target, .. } => {
|
||||||
matches!(target, RuntimeCompanyTarget::AllActive)
|
matches!(target, RuntimeCompanyTarget::AllActive)
|
||||||
}
|
}
|
||||||
|
|
@ -7356,7 +7575,10 @@ mod tests {
|
||||||
assert_eq!(summary.records[0].text_bands[0].preview, "Alpha");
|
assert_eq!(summary.records[0].text_bands[0].preview, "Alpha");
|
||||||
assert_eq!(summary.records[0].standalone_condition_row_count, 0);
|
assert_eq!(summary.records[0].standalone_condition_row_count, 0);
|
||||||
assert_eq!(summary.records[0].standalone_condition_rows.len(), 0);
|
assert_eq!(summary.records[0].standalone_condition_rows.len(), 0);
|
||||||
assert_eq!(summary.records[0].grouped_effect_row_counts, vec![0, 0, 0, 0]);
|
assert_eq!(
|
||||||
|
summary.records[0].grouped_effect_row_counts,
|
||||||
|
vec![0, 0, 0, 0]
|
||||||
|
);
|
||||||
assert_eq!(summary.records[0].grouped_effect_rows.len(), 0);
|
assert_eq!(summary.records[0].grouped_effect_rows.len(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -7422,7 +7644,10 @@ mod tests {
|
||||||
.grouped_target_scope_ordinals_0x7fb,
|
.grouped_target_scope_ordinals_0x7fb,
|
||||||
vec![1, 4, 7, 8]
|
vec![1, 4, 7, 8]
|
||||||
);
|
);
|
||||||
assert_eq!(summary.records[0].standalone_condition_rows[0].raw_condition_id, -1);
|
assert_eq!(
|
||||||
|
summary.records[0].standalone_condition_rows[0].raw_condition_id,
|
||||||
|
-1
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
summary.records[0].standalone_condition_rows[0]
|
summary.records[0].standalone_condition_rows[0]
|
||||||
.candidate_name
|
.candidate_name
|
||||||
|
|
@ -7431,16 +7656,173 @@ mod tests {
|
||||||
);
|
);
|
||||||
assert_eq!(summary.records[0].grouped_effect_rows.len(), 1);
|
assert_eq!(summary.records[0].grouped_effect_rows.len(), 1);
|
||||||
assert_eq!(summary.records[0].grouped_effect_rows[0].opcode, 8);
|
assert_eq!(summary.records[0].grouped_effect_rows[0].opcode, 8);
|
||||||
|
assert_eq!(
|
||||||
|
summary.records[0].grouped_effect_rows[0]
|
||||||
|
.descriptor_label
|
||||||
|
.as_deref(),
|
||||||
|
Some("Company Cash")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
summary.records[0].grouped_effect_rows[0].target_mask_bits,
|
||||||
|
Some(0x01)
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
summary.records[0].grouped_effect_rows[0].row_shape,
|
summary.records[0].grouped_effect_rows[0].row_shape,
|
||||||
"multivalue_scalar"
|
"multivalue_scalar"
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
summary.records[0].grouped_effect_rows[0]
|
||||||
|
.semantic_family
|
||||||
|
.as_deref(),
|
||||||
|
Some("multivalue_scalar")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
summary.records[0].grouped_effect_rows[0]
|
||||||
|
.semantic_preview
|
||||||
|
.as_deref(),
|
||||||
|
Some("Set Company Cash to 7 with aux [2, 3, 24, 36]")
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
summary.records[0].grouped_effect_rows[0]
|
summary.records[0].grouped_effect_rows[0]
|
||||||
.locomotive_name
|
.locomotive_name
|
||||||
.as_deref(),
|
.as_deref(),
|
||||||
Some("Mikado")
|
Some("Mikado")
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
summary.records[0].decoded_actions,
|
||||||
|
vec![RuntimeEffect::SetCompanyCash {
|
||||||
|
target: RuntimeCompanyTarget::SelectedCompany,
|
||||||
|
value: 7,
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn classifies_real_grouped_row_semantic_families() {
|
||||||
|
let grouped_rows = vec![
|
||||||
|
build_real_grouped_effect_row(RealGroupedEffectRowSpec {
|
||||||
|
descriptor_id: 2,
|
||||||
|
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,
|
||||||
|
locomotive_name: None,
|
||||||
|
}),
|
||||||
|
build_real_grouped_effect_row(RealGroupedEffectRowSpec {
|
||||||
|
descriptor_id: 2,
|
||||||
|
opcode: 4,
|
||||||
|
raw_scalar_value: 25,
|
||||||
|
value_byte_0x09: 0,
|
||||||
|
value_dword_0x0d: 0,
|
||||||
|
value_byte_0x11: 0,
|
||||||
|
value_byte_0x12: 0,
|
||||||
|
value_word_0x14: 2,
|
||||||
|
value_word_0x16: 6,
|
||||||
|
locomotive_name: None,
|
||||||
|
}),
|
||||||
|
build_real_grouped_effect_row(RealGroupedEffectRowSpec {
|
||||||
|
descriptor_id: 2,
|
||||||
|
opcode: 3,
|
||||||
|
raw_scalar_value: 250,
|
||||||
|
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,
|
||||||
|
}),
|
||||||
|
build_real_grouped_effect_row(RealGroupedEffectRowSpec {
|
||||||
|
descriptor_id: 2,
|
||||||
|
opcode: 8,
|
||||||
|
raw_scalar_value: 7,
|
||||||
|
value_byte_0x09: 1,
|
||||||
|
value_dword_0x0d: 12,
|
||||||
|
value_byte_0x11: 2,
|
||||||
|
value_byte_0x12: 3,
|
||||||
|
value_word_0x14: 24,
|
||||||
|
value_word_0x16: 36,
|
||||||
|
locomotive_name: Some("Mikado"),
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
let record_body = build_real_event_record(
|
||||||
|
[b"Semantic", b"", b"", b"", b"", b""],
|
||||||
|
Some(RealCompactControlSpec {
|
||||||
|
mode_byte_0x7ef: 7,
|
||||||
|
primary_selector_0x7f0: 0x63,
|
||||||
|
grouped_mode_0x7f4: 1,
|
||||||
|
one_shot_header_0x7f5: 0,
|
||||||
|
modifier_flag_0x7f9: 0,
|
||||||
|
modifier_flag_0x7fa: 0,
|
||||||
|
grouped_target_scope_ordinals_0x7fb: [1, 1, 1, 1],
|
||||||
|
grouped_scope_checkboxes_0x7ff: [0, 0, 0, 0],
|
||||||
|
summary_toggle_0x800: 0,
|
||||||
|
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
|
||||||
|
}),
|
||||||
|
&[],
|
||||||
|
[&grouped_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");
|
||||||
|
let families = summary.records[0]
|
||||||
|
.grouped_effect_rows
|
||||||
|
.iter()
|
||||||
|
.map(|row| row.semantic_family.as_deref().unwrap_or(""))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
assert_eq!(
|
||||||
|
families,
|
||||||
|
vec![
|
||||||
|
"bool_toggle",
|
||||||
|
"timed_duration",
|
||||||
|
"scalar_assignment",
|
||||||
|
"multivalue_scalar",
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
summary.records[0].grouped_effect_rows[0]
|
||||||
|
.semantic_preview
|
||||||
|
.as_deref(),
|
||||||
|
Some("Set Company Cash to TRUE")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
summary.records[0].grouped_effect_rows[1]
|
||||||
|
.semantic_preview
|
||||||
|
.as_deref(),
|
||||||
|
Some("Set Company Cash to 25 for 2 years 6 months")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
summary.records[0].grouped_effect_rows[2]
|
||||||
|
.semantic_preview
|
||||||
|
.as_deref(),
|
||||||
|
Some("Set Company Cash to 250")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
summary.records[0].grouped_effect_rows[3]
|
||||||
|
.semantic_preview
|
||||||
|
.as_deref(),
|
||||||
|
Some("Set Company Cash to 7 with aux [2, 3, 24, 36]")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,7 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
RuntimeCompanyControllerKind, RuntimeCompanyTarget, RuntimeEffect, RuntimeEventRecordTemplate,
|
RuntimeCompanyControllerKind, RuntimeCompanyTarget, RuntimeEffect, RuntimeEventRecordTemplate,
|
||||||
RuntimeState, RuntimeSummary,
|
RuntimeState, RuntimeSummary, calendar::BoundaryEventKind,
|
||||||
calendar::BoundaryEventKind,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const PERIODIC_TRIGGER_KIND_ORDER: [u8; 6] = [1, 0, 3, 2, 5, 4];
|
const PERIODIC_TRIGGER_KIND_ORDER: [u8; 6] = [1, 0, 3, 2, 5, 4];
|
||||||
|
|
@ -286,6 +285,20 @@ fn apply_runtime_effects(
|
||||||
RuntimeEffect::SetWorldFlag { key, value } => {
|
RuntimeEffect::SetWorldFlag { key, value } => {
|
||||||
state.world_flags.insert(key.clone(), *value);
|
state.world_flags.insert(key.clone(), *value);
|
||||||
}
|
}
|
||||||
|
RuntimeEffect::SetCompanyCash { 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 cash effect")
|
||||||
|
})?;
|
||||||
|
company.current_cash = *value;
|
||||||
|
mutated_company_ids.insert(company_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
RuntimeEffect::AdjustCompanyCash { target, delta } => {
|
RuntimeEffect::AdjustCompanyCash { target, delta } => {
|
||||||
let company_ids = resolve_company_target_ids(state, target)?;
|
let company_ids = resolve_company_target_ids(state, target)?;
|
||||||
for company_id in company_ids {
|
for company_id in company_ids {
|
||||||
|
|
|
||||||
|
|
@ -77,11 +77,11 @@ The highest-value next passes are now:
|
||||||
avoid shell-first implementation bets
|
avoid shell-first implementation bets
|
||||||
- keep using overlay imports as the context bridge when selectively executable packed rows still
|
- keep using overlay imports as the context bridge when selectively executable packed rows still
|
||||||
need live company state that save slices do not persist
|
need live company state that save slices do not persist
|
||||||
- treat normalized symbolic company targets as the active packed-event frontier now that
|
- treat broader real grouped-descriptor recovery as the active packed-event frontier now that
|
||||||
`selected_company`, `human_companies`, and `ai_companies` import and execute through the runtime
|
descriptor `2` `Company Cash` already parses, summarizes, and executes through the ordinary
|
||||||
service path
|
runtime path when overlay context resolves its symbolic company scope
|
||||||
- widen real packed-event executable coverage only after the compact-control, symbolic target, and
|
- widen real packed-event executable coverage descriptor by descriptor after identity, target mask,
|
||||||
descriptor frontier is stable, not just after row framing is parsed
|
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
|
- leave condition-relative company scopes explicit and blocked until condition evaluation has
|
||||||
grounded runtime semantics
|
grounded runtime semantics
|
||||||
- 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,
|
||||||
|
|
|
||||||
|
|
@ -28,10 +28,12 @@ Implemented today:
|
||||||
company-target model can execute `selected_company`, `human_companies`, and `ai_companies`
|
company-target model can execute `selected_company`, `human_companies`, and `ai_companies`
|
||||||
symbolic scopes through the ordinary runtime service path while keeping condition-relative
|
symbolic scopes through the ordinary runtime service path while keeping condition-relative
|
||||||
company scopes explicitly blocked
|
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`
|
||||||
|
|
||||||
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
|
||||||
real grouped-descriptor semantic mapping on top of the now-stable compact-control and symbolic
|
broader real grouped-descriptor coverage beyond `Company Cash`, plus condition-relative execution
|
||||||
target frontier, not another persistence scaffold pass.
|
for the still-blocked symbolic scopes, not another persistence scaffold pass.
|
||||||
|
|
||||||
## Why This Boundary
|
## Why This Boundary
|
||||||
|
|
||||||
|
|
@ -372,45 +374,44 @@ Checked-in fixture families already include:
|
||||||
|
|
||||||
## Next Slice
|
## Next Slice
|
||||||
|
|
||||||
The recommended next implementation slice is real `0x4e9a` compact-control decode on top of the
|
The recommended next implementation slice is broader real grouped-descriptor coverage on top of the
|
||||||
existing real-row structural parse.
|
now-stable compact-control, symbolic-target, and first recovered real-family path.
|
||||||
|
|
||||||
Target behavior:
|
Target behavior:
|
||||||
|
|
||||||
- decode the compact control block that sits above the real standalone-condition and grouped-effect
|
- keep descriptor `2` `Company Cash` as the proof that real grouped rows can cross the whole path:
|
||||||
row families, carrying through raw grounded lanes such as mode byte `0x7ef`, primary selector
|
parse, semantic summary, overlay-backed import, and ordinary trigger execution
|
||||||
`0x7f0`, grouped mode `0x7f4`, one-shot header `0x7f5`, modifier bytes `0x7f9/0x7fa`, grouped
|
- recover more real descriptor identities from the checked-in effect table and expose their target
|
||||||
target-scope ordinals `0x7fb`, grouped scope checkboxes `0x7ff`, summary toggle `0x800`, and
|
masks and conservative semantic previews without guessing unsupported behavior
|
||||||
grouped territory selectors `0x80f`
|
- widen executable real import only when both descriptor identity and runtime effect semantics are
|
||||||
- keep real rows parity-only in runtime import, but replace the coarse `blocked_structural_only`
|
grounded enough to map into the normalized runtime path honestly
|
||||||
frontier with narrower outcomes such as `blocked_missing_compact_control` and
|
- keep condition-relative company scopes explicit until a real condition evaluator exists, instead
|
||||||
`blocked_unmapped_real_descriptor`
|
of silently degrading or inventing target resolution
|
||||||
- keep the existing synthetic harness and overlay-backed executable import path working unchanged
|
|
||||||
- reserve the first real descriptor-to-effect mapping for a later slice once captured evidence is
|
|
||||||
tighter
|
|
||||||
|
|
||||||
Public-model additions for that slice:
|
Public-model expectations for that slice:
|
||||||
|
|
||||||
- compact-control summaries on packed-event records in both the save-side and runtime-side models
|
- additional checked-in grouped-descriptor metadata entries keyed by recovered descriptor id
|
||||||
- runtime summary counts for compact-control-missing and unmapped-real-descriptor blockers
|
- more parity summaries with real descriptor labels, target masks, parameter families, and semantic
|
||||||
- trigger-kind and one-shot derivation only where the compact-control mapping is already grounded
|
previews
|
||||||
|
- more selective real-row `decoded_actions` only where the descriptor-to-runtime mapping is
|
||||||
|
supported end to end
|
||||||
|
|
||||||
Fixture work for that slice:
|
Fixture work for that slice:
|
||||||
|
|
||||||
- update the parity-heavy tracked sample so the real row includes compact-control state
|
- preserve the parity-heavy tracked sample as the condition-relative blocked frontier while it now
|
||||||
- regression fixtures that keep synthetic executable import and overlay-backed company-context
|
carries recovered `Company Cash` semantics
|
||||||
upgrade behavior green
|
- add overlay-backed captured fixtures whenever a new real descriptor family becomes executable
|
||||||
- state-fragment assertions that lock the new compact-control summary and narrower import blockers
|
- keep synthetic harness, save-slice, and overlay paths green as the real descriptor surface widens
|
||||||
|
|
||||||
Current local constraint:
|
Current local constraint:
|
||||||
|
|
||||||
- the local checked-in and on-disk `.gms` corpus currently exports with
|
- the local checked-in and on-disk `.gms` corpus still does not provide a richer captured packed
|
||||||
`packed_event_collection_present = false`, so this slice must not depend on a newly captured real
|
event save set, so descriptor recovery must continue to rely on the grounded static tables and
|
||||||
packed-event-bearing save for acceptance
|
tracked JSON artifacts until new captures exist
|
||||||
|
|
||||||
Do not mix this slice with:
|
Do not mix this slice with:
|
||||||
|
|
||||||
- territory-access or selected-profile parity
|
|
||||||
- placed-structure batch placement parity
|
|
||||||
- shell queue/modal behavior
|
- shell queue/modal behavior
|
||||||
- broad speculative translation of real packed RT3 event rows into executable normalized effects
|
- territory-access or selected-profile parity
|
||||||
|
- broad condition evaluation without grounded runtime ownership
|
||||||
|
- speculative executable import for real rows whose descriptor meaning is still weak
|
||||||
|
|
|
||||||
|
|
@ -70,9 +70,23 @@
|
||||||
"candidate_name": "AutoPlant"
|
"candidate_name": "AutoPlant"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"decoded_actions": [
|
||||||
|
{
|
||||||
|
"kind": "set_company_cash",
|
||||||
|
"target": {
|
||||||
|
"kind": "condition_true_company"
|
||||||
|
},
|
||||||
|
"value": 7
|
||||||
|
}
|
||||||
|
],
|
||||||
"grouped_effect_rows": [
|
"grouped_effect_rows": [
|
||||||
{
|
{
|
||||||
|
"descriptor_label": "Company Cash",
|
||||||
|
"target_mask_bits": 1,
|
||||||
|
"parameter_family": "company_finance_scalar",
|
||||||
"row_shape": "multivalue_scalar",
|
"row_shape": "multivalue_scalar",
|
||||||
|
"semantic_family": "multivalue_scalar",
|
||||||
|
"semantic_preview": "Set Company Cash to 7 with aux [2, 3, 24, 36]",
|
||||||
"locomotive_name": "Mikado"
|
"locomotive_name": "Mikado"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
"original_save_sha256": "parity-sample-sha256",
|
"original_save_sha256": "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",
|
||||||
"preserves one unsupported row and one decoded-but-parity-only row"
|
"preserves one unsupported row and one semantically decoded-but-parity-only row"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"save_slice": {
|
"save_slice": {
|
||||||
|
|
@ -133,6 +133,9 @@
|
||||||
"group_index": 0,
|
"group_index": 0,
|
||||||
"row_index": 0,
|
"row_index": 0,
|
||||||
"descriptor_id": 2,
|
"descriptor_id": 2,
|
||||||
|
"descriptor_label": "Company Cash",
|
||||||
|
"target_mask_bits": 1,
|
||||||
|
"parameter_family": "company_finance_scalar",
|
||||||
"opcode": 8,
|
"opcode": 8,
|
||||||
"raw_scalar_value": 7,
|
"raw_scalar_value": 7,
|
||||||
"value_byte_0x09": 1,
|
"value_byte_0x09": 1,
|
||||||
|
|
@ -142,16 +145,27 @@
|
||||||
"value_word_0x14": 24,
|
"value_word_0x14": 24,
|
||||||
"value_word_0x16": 36,
|
"value_word_0x16": 36,
|
||||||
"row_shape": "multivalue_scalar",
|
"row_shape": "multivalue_scalar",
|
||||||
|
"semantic_family": "multivalue_scalar",
|
||||||
|
"semantic_preview": "Set Company Cash to 7 with aux [2, 3, 24, 36]",
|
||||||
"locomotive_name": "Mikado",
|
"locomotive_name": "Mikado",
|
||||||
"notes": [
|
"notes": [
|
||||||
"grouped effect row carries locomotive-name side string"
|
"grouped effect row carries locomotive-name side string"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"decoded_actions": [],
|
"decoded_actions": [
|
||||||
|
{
|
||||||
|
"kind": "set_company_cash",
|
||||||
|
"target": {
|
||||||
|
"kind": "condition_true_company"
|
||||||
|
},
|
||||||
|
"value": 7
|
||||||
|
}
|
||||||
|
],
|
||||||
"executable_import_ready": false,
|
"executable_import_ready": false,
|
||||||
"notes": [
|
"notes": [
|
||||||
"decoded from grounded real 0x4e9a row framing with compact control"
|
"decoded from grounded real 0x4e9a row framing",
|
||||||
|
"grouped descriptor labels and target masks come from the checked-in effect table recovered around 0x006103a0"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue