Add symbolic company target runtime import
This commit is contained in:
parent
4ff6d65774
commit
f918d0c4f7
17 changed files with 1230 additions and 80 deletions
|
|
@ -11,9 +11,12 @@ 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 `0x4e9a` compact-control decode and descriptor-frontier tightening on top of the
|
frontier is real grouped-descriptor semantic mapping on top of the existing save-slice, snapshot,
|
||||||
existing save-slice, snapshot, and overlay-import workflows. The PE32 hook remains useful as
|
overlay-import, compact-control, and symbolic company-target workflows. The runtime already carries
|
||||||
capture and integration tooling, but it is no longer the main execution milestone.
|
selected-company and controller-role context through overlay imports so synthetic packed records can
|
||||||
|
execute `selected_company`, `human_companies`, and `ai_companies` scopes without a parallel packed
|
||||||
|
executor, while condition-relative company scopes remain explicitly blocked. The PE32 hook remains
|
||||||
|
useful as capture and integration tooling, but it is no longer the main execution milestone.
|
||||||
|
|
||||||
## Project Docs
|
## Project Docs
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4440,6 +4440,8 @@ mod tests {
|
||||||
.join("../../fixtures/runtime/packed-event-selective-import-save-slice-fixture.json");
|
.join("../../fixtures/runtime/packed-event-selective-import-save-slice-fixture.json");
|
||||||
let overlay_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
let overlay_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||||
.join("../../fixtures/runtime/packed-event-selective-import-overlay-fixture.json");
|
.join("../../fixtures/runtime/packed-event-selective-import-overlay-fixture.json");
|
||||||
|
let symbolic_overlay_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||||
|
.join("../../fixtures/runtime/packed-event-symbolic-company-scope-overlay-fixture.json");
|
||||||
|
|
||||||
run_runtime_summarize_fixture(&parity_fixture)
|
run_runtime_summarize_fixture(&parity_fixture)
|
||||||
.expect("save-slice-backed parity fixture should summarize");
|
.expect("save-slice-backed parity fixture should summarize");
|
||||||
|
|
@ -4447,6 +4449,8 @@ mod tests {
|
||||||
.expect("save-slice-backed selective-import fixture should summarize");
|
.expect("save-slice-backed selective-import fixture should summarize");
|
||||||
run_runtime_summarize_fixture(&overlay_fixture)
|
run_runtime_summarize_fixture(&overlay_fixture)
|
||||||
.expect("overlay-backed selective-import fixture should summarize");
|
.expect("overlay-backed selective-import fixture should summarize");
|
||||||
|
run_runtime_summarize_fixture(&symbolic_overlay_fixture)
|
||||||
|
.expect("overlay-backed symbolic-target fixture should summarize");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -173,6 +173,7 @@ mod tests {
|
||||||
world_restore: RuntimeWorldRestoreState::default(),
|
world_restore: RuntimeWorldRestoreState::default(),
|
||||||
metadata: BTreeMap::new(),
|
metadata: BTreeMap::new(),
|
||||||
companies: Vec::new(),
|
companies: Vec::new(),
|
||||||
|
selected_company_id: None,
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
@ -326,9 +327,11 @@ mod tests {
|
||||||
metadata: BTreeMap::new(),
|
metadata: BTreeMap::new(),
|
||||||
companies: vec![rrt_runtime::RuntimeCompany {
|
companies: vec![rrt_runtime::RuntimeCompany {
|
||||||
company_id: 42,
|
company_id: 42,
|
||||||
|
controller_kind: rrt_runtime::RuntimeCompanyControllerKind::Human,
|
||||||
current_cash: 100,
|
current_cash: 100,
|
||||||
debt: 0,
|
debt: 0,
|
||||||
}],
|
}],
|
||||||
|
selected_company_id: Some(42),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,12 @@ pub struct ExpectedRuntimeSummary {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub packed_event_blocked_missing_company_context_count: Option<usize>,
|
pub packed_event_blocked_missing_company_context_count: Option<usize>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub packed_event_blocked_missing_selection_context_count: Option<usize>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub packed_event_blocked_missing_company_role_context_count: Option<usize>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub packed_event_blocked_missing_condition_context_count: Option<usize>,
|
||||||
|
#[serde(default)]
|
||||||
pub packed_event_blocked_missing_compact_control_count: Option<usize>,
|
pub packed_event_blocked_missing_compact_control_count: Option<usize>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub packed_event_blocked_unmapped_real_descriptor_count: Option<usize>,
|
pub packed_event_blocked_unmapped_real_descriptor_count: Option<usize>,
|
||||||
|
|
@ -377,6 +383,30 @@ impl ExpectedRuntimeSummary {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(count) = self.packed_event_blocked_missing_selection_context_count {
|
||||||
|
if actual.packed_event_blocked_missing_selection_context_count != count {
|
||||||
|
mismatches.push(format!(
|
||||||
|
"packed_event_blocked_missing_selection_context_count mismatch: expected {count}, got {}",
|
||||||
|
actual.packed_event_blocked_missing_selection_context_count
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(count) = self.packed_event_blocked_missing_company_role_context_count {
|
||||||
|
if actual.packed_event_blocked_missing_company_role_context_count != count {
|
||||||
|
mismatches.push(format!(
|
||||||
|
"packed_event_blocked_missing_company_role_context_count mismatch: expected {count}, got {}",
|
||||||
|
actual.packed_event_blocked_missing_company_role_context_count
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(count) = self.packed_event_blocked_missing_condition_context_count {
|
||||||
|
if actual.packed_event_blocked_missing_condition_context_count != count {
|
||||||
|
mismatches.push(format!(
|
||||||
|
"packed_event_blocked_missing_condition_context_count mismatch: expected {count}, got {}",
|
||||||
|
actual.packed_event_blocked_missing_condition_context_count
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Some(count) = self.packed_event_blocked_missing_compact_control_count {
|
if let Some(count) = self.packed_event_blocked_missing_compact_control_count {
|
||||||
if actual.packed_event_blocked_missing_compact_control_count != count {
|
if actual.packed_event_blocked_missing_compact_control_count != count {
|
||||||
mismatches.push(format!(
|
mismatches.push(format!(
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@ 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, RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate,
|
CalendarPoint, RuntimeCompanyControllerKind, RuntimeCompanyTarget, RuntimeEffect,
|
||||||
|
RuntimeEventRecord, RuntimeEventRecordTemplate,
|
||||||
RuntimePackedEventCompactControlSummary,
|
RuntimePackedEventCompactControlSummary,
|
||||||
RuntimePackedEventConditionRowSummary, RuntimePackedEventGroupedEffectRowSummary,
|
RuntimePackedEventConditionRowSummary, RuntimePackedEventGroupedEffectRowSummary,
|
||||||
RuntimePackedEventCollectionSummary, RuntimePackedEventRecordSummary,
|
RuntimePackedEventCollectionSummary, RuntimePackedEventRecordSummary,
|
||||||
|
|
@ -99,6 +100,47 @@ enum SaveSliceProjectionMode {
|
||||||
Overlay,
|
Overlay,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
struct ImportCompanyContext {
|
||||||
|
known_company_ids: BTreeSet<u32>,
|
||||||
|
selected_company_id: Option<u32>,
|
||||||
|
has_complete_controller_context: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum CompanyTargetImportBlocker {
|
||||||
|
MissingCompanyContext,
|
||||||
|
MissingSelectionContext,
|
||||||
|
MissingCompanyRoleContext,
|
||||||
|
MissingConditionContext,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImportCompanyContext {
|
||||||
|
fn standalone() -> Self {
|
||||||
|
Self {
|
||||||
|
known_company_ids: BTreeSet::new(),
|
||||||
|
selected_company_id: None,
|
||||||
|
has_complete_controller_context: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_runtime_state(state: &RuntimeState) -> Self {
|
||||||
|
Self {
|
||||||
|
known_company_ids: state
|
||||||
|
.companies
|
||||||
|
.iter()
|
||||||
|
.map(|company| company.company_id)
|
||||||
|
.collect(),
|
||||||
|
selected_company_id: state.selected_company_id,
|
||||||
|
has_complete_controller_context: !state.companies.is_empty()
|
||||||
|
&& state
|
||||||
|
.companies
|
||||||
|
.iter()
|
||||||
|
.all(|company| company.controller_kind != RuntimeCompanyControllerKind::Unknown),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn project_save_slice_to_runtime_state_import(
|
pub fn project_save_slice_to_runtime_state_import(
|
||||||
save_slice: &SmpLoadedSaveSlice,
|
save_slice: &SmpLoadedSaveSlice,
|
||||||
import_id: &str,
|
import_id: &str,
|
||||||
|
|
@ -109,7 +151,7 @@ pub fn project_save_slice_to_runtime_state_import(
|
||||||
}
|
}
|
||||||
let projection = project_save_slice_components(
|
let projection = project_save_slice_components(
|
||||||
save_slice,
|
save_slice,
|
||||||
&BTreeSet::new(),
|
&ImportCompanyContext::standalone(),
|
||||||
SaveSliceProjectionMode::Standalone,
|
SaveSliceProjectionMode::Standalone,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|
@ -125,6 +167,7 @@ pub fn project_save_slice_to_runtime_state_import(
|
||||||
world_restore: projection.world_restore,
|
world_restore: projection.world_restore,
|
||||||
metadata: projection.metadata,
|
metadata: projection.metadata,
|
||||||
companies: Vec::new(),
|
companies: Vec::new(),
|
||||||
|
selected_company_id: None,
|
||||||
packed_event_collection: projection.packed_event_collection,
|
packed_event_collection: projection.packed_event_collection,
|
||||||
event_runtime_records: projection.event_runtime_records,
|
event_runtime_records: projection.event_runtime_records,
|
||||||
candidate_availability: projection.candidate_availability,
|
candidate_availability: projection.candidate_availability,
|
||||||
|
|
@ -151,14 +194,10 @@ pub fn project_save_slice_overlay_to_runtime_state_import(
|
||||||
}
|
}
|
||||||
base_state.validate()?;
|
base_state.validate()?;
|
||||||
|
|
||||||
let known_company_ids = base_state
|
let company_context = ImportCompanyContext::from_runtime_state(base_state);
|
||||||
.companies
|
|
||||||
.iter()
|
|
||||||
.map(|company| company.company_id)
|
|
||||||
.collect::<BTreeSet<_>>();
|
|
||||||
let projection = project_save_slice_components(
|
let projection = project_save_slice_components(
|
||||||
save_slice,
|
save_slice,
|
||||||
&known_company_ids,
|
&company_context,
|
||||||
SaveSliceProjectionMode::Overlay,
|
SaveSliceProjectionMode::Overlay,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|
@ -177,6 +216,7 @@ pub fn project_save_slice_overlay_to_runtime_state_import(
|
||||||
world_restore: projection.world_restore,
|
world_restore: projection.world_restore,
|
||||||
metadata,
|
metadata,
|
||||||
companies: base_state.companies.clone(),
|
companies: base_state.companies.clone(),
|
||||||
|
selected_company_id: base_state.selected_company_id,
|
||||||
packed_event_collection: projection.packed_event_collection,
|
packed_event_collection: projection.packed_event_collection,
|
||||||
event_runtime_records: projection.event_runtime_records,
|
event_runtime_records: projection.event_runtime_records,
|
||||||
candidate_availability: projection.candidate_availability,
|
candidate_availability: projection.candidate_availability,
|
||||||
|
|
@ -194,7 +234,7 @@ pub fn project_save_slice_overlay_to_runtime_state_import(
|
||||||
|
|
||||||
fn project_save_slice_components(
|
fn project_save_slice_components(
|
||||||
save_slice: &SmpLoadedSaveSlice,
|
save_slice: &SmpLoadedSaveSlice,
|
||||||
known_company_ids: &BTreeSet<u32>,
|
company_context: &ImportCompanyContext,
|
||||||
mode: SaveSliceProjectionMode,
|
mode: SaveSliceProjectionMode,
|
||||||
) -> Result<SaveSliceProjection, String> {
|
) -> Result<SaveSliceProjection, String> {
|
||||||
let mut world_flags = BTreeMap::new();
|
let mut world_flags = BTreeMap::new();
|
||||||
|
|
@ -301,7 +341,7 @@ fn project_save_slice_components(
|
||||||
}
|
}
|
||||||
|
|
||||||
let (packed_event_collection, event_runtime_records) =
|
let (packed_event_collection, event_runtime_records) =
|
||||||
project_packed_event_collection(save_slice, known_company_ids)?;
|
project_packed_event_collection(save_slice, company_context)?;
|
||||||
if let Some(summary) = &save_slice.event_runtime_collection {
|
if let Some(summary) = &save_slice.event_runtime_collection {
|
||||||
metadata.insert(
|
metadata.insert(
|
||||||
"save_slice.event_runtime_collection_source_kind".to_string(),
|
"save_slice.event_runtime_collection_source_kind".to_string(),
|
||||||
|
|
@ -491,7 +531,7 @@ fn project_save_slice_components(
|
||||||
|
|
||||||
fn project_packed_event_collection(
|
fn project_packed_event_collection(
|
||||||
save_slice: &SmpLoadedSaveSlice,
|
save_slice: &SmpLoadedSaveSlice,
|
||||||
known_company_ids: &BTreeSet<u32>,
|
company_context: &ImportCompanyContext,
|
||||||
) -> Result<
|
) -> Result<
|
||||||
(
|
(
|
||||||
Option<RuntimePackedEventCollectionSummary>,
|
Option<RuntimePackedEventCollectionSummary>,
|
||||||
|
|
@ -506,9 +546,7 @@ 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) =
|
if let Some(import_result) = smp_packed_record_to_runtime_event_record(record, company_context) {
|
||||||
smp_packed_record_to_runtime_event_record(record, known_company_ids)
|
|
||||||
{
|
|
||||||
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);
|
||||||
|
|
@ -521,7 +559,7 @@ fn project_packed_event_collection(
|
||||||
.map(|record| {
|
.map(|record| {
|
||||||
runtime_packed_event_record_summary_from_smp(
|
runtime_packed_event_record_summary_from_smp(
|
||||||
record,
|
record,
|
||||||
known_company_ids,
|
company_context,
|
||||||
imported_record_ids.contains(&record.live_entry_id),
|
imported_record_ids.contains(&record.live_entry_id),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
@ -551,7 +589,7 @@ fn project_packed_event_collection(
|
||||||
|
|
||||||
fn runtime_packed_event_record_summary_from_smp(
|
fn runtime_packed_event_record_summary_from_smp(
|
||||||
record: &SmpLoadedPackedEventRecordSummary,
|
record: &SmpLoadedPackedEventRecordSummary,
|
||||||
known_company_ids: &BTreeSet<u32>,
|
company_context: &ImportCompanyContext,
|
||||||
imported: bool,
|
imported: bool,
|
||||||
) -> RuntimePackedEventRecordSummary {
|
) -> RuntimePackedEventRecordSummary {
|
||||||
RuntimePackedEventRecordSummary {
|
RuntimePackedEventRecordSummary {
|
||||||
|
|
@ -586,11 +624,12 @@ fn runtime_packed_event_record_summary_from_smp(
|
||||||
.iter()
|
.iter()
|
||||||
.map(runtime_packed_event_grouped_effect_row_summary_from_smp)
|
.map(runtime_packed_event_grouped_effect_row_summary_from_smp)
|
||||||
.collect(),
|
.collect(),
|
||||||
|
grouped_company_targets: classify_real_grouped_company_targets(record),
|
||||||
decoded_actions: record.decoded_actions.clone(),
|
decoded_actions: record.decoded_actions.clone(),
|
||||||
executable_import_ready: record.executable_import_ready,
|
executable_import_ready: record.executable_import_ready,
|
||||||
import_outcome: Some(determine_packed_event_import_outcome(
|
import_outcome: Some(determine_packed_event_import_outcome(
|
||||||
record,
|
record,
|
||||||
known_company_ids,
|
company_context,
|
||||||
imported,
|
imported,
|
||||||
)),
|
)),
|
||||||
notes: record.notes.clone(),
|
notes: record.notes.clone(),
|
||||||
|
|
@ -661,18 +700,16 @@ fn runtime_packed_event_grouped_effect_row_summary_from_smp(
|
||||||
|
|
||||||
fn smp_packed_record_to_runtime_event_record(
|
fn smp_packed_record_to_runtime_event_record(
|
||||||
record: &SmpLoadedPackedEventRecordSummary,
|
record: &SmpLoadedPackedEventRecordSummary,
|
||||||
known_company_ids: &BTreeSet<u32>,
|
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" || record.payload_family == "real_packed_v1" {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let effects =
|
let effects = match smp_runtime_effects_to_runtime_effects(&record.decoded_actions, company_context) {
|
||||||
match smp_runtime_effects_to_runtime_effects(&record.decoded_actions, known_company_ids) {
|
Ok(effects) => effects,
|
||||||
Ok(effects) => effects,
|
Err(_) => return None,
|
||||||
Err(err) if err.contains("unresolved company ids") => return None,
|
};
|
||||||
Err(_) => return None,
|
|
||||||
};
|
|
||||||
|
|
||||||
Some((|| {
|
Some((|| {
|
||||||
let trigger_kind = record.trigger_kind.ok_or_else(|| {
|
let trigger_kind = record.trigger_kind.ok_or_else(|| {
|
||||||
|
|
@ -713,17 +750,17 @@ fn smp_packed_record_to_runtime_event_record(
|
||||||
|
|
||||||
fn smp_runtime_effects_to_runtime_effects(
|
fn smp_runtime_effects_to_runtime_effects(
|
||||||
effects: &[RuntimeEffect],
|
effects: &[RuntimeEffect],
|
||||||
known_company_ids: &BTreeSet<u32>,
|
company_context: &ImportCompanyContext,
|
||||||
) -> Result<Vec<RuntimeEffect>, String> {
|
) -> Result<Vec<RuntimeEffect>, String> {
|
||||||
effects
|
effects
|
||||||
.iter()
|
.iter()
|
||||||
.map(|effect| smp_runtime_effect_to_runtime_effect(effect, known_company_ids))
|
.map(|effect| smp_runtime_effect_to_runtime_effect(effect, company_context))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn smp_runtime_effect_to_runtime_effect(
|
fn smp_runtime_effect_to_runtime_effect(
|
||||||
effect: &RuntimeEffect,
|
effect: &RuntimeEffect,
|
||||||
known_company_ids: &BTreeSet<u32>,
|
company_context: &ImportCompanyContext,
|
||||||
) -> Result<RuntimeEffect, String> {
|
) -> Result<RuntimeEffect, String> {
|
||||||
match effect {
|
match effect {
|
||||||
RuntimeEffect::SetWorldFlag { key, value } => Ok(RuntimeEffect::SetWorldFlag {
|
RuntimeEffect::SetWorldFlag { key, value } => Ok(RuntimeEffect::SetWorldFlag {
|
||||||
|
|
@ -731,23 +768,23 @@ fn smp_runtime_effect_to_runtime_effect(
|
||||||
value: *value,
|
value: *value,
|
||||||
}),
|
}),
|
||||||
RuntimeEffect::AdjustCompanyCash { target, delta } => {
|
RuntimeEffect::AdjustCompanyCash { target, delta } => {
|
||||||
if company_target_supported_for_import(target, known_company_ids) {
|
if company_target_import_blocker(target, company_context).is_none() {
|
||||||
Ok(RuntimeEffect::AdjustCompanyCash {
|
Ok(RuntimeEffect::AdjustCompanyCash {
|
||||||
target: target.clone(),
|
target: target.clone(),
|
||||||
delta: *delta,
|
delta: *delta,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Err("packed company-cash effect requires unresolved company ids".to_string())
|
Err(company_target_import_error_message(target, company_context))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RuntimeEffect::AdjustCompanyDebt { target, delta } => {
|
RuntimeEffect::AdjustCompanyDebt { target, delta } => {
|
||||||
if company_target_supported_for_import(target, known_company_ids) {
|
if company_target_import_blocker(target, company_context).is_none() {
|
||||||
Ok(RuntimeEffect::AdjustCompanyDebt {
|
Ok(RuntimeEffect::AdjustCompanyDebt {
|
||||||
target: target.clone(),
|
target: target.clone(),
|
||||||
delta: *delta,
|
delta: *delta,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Err("packed company-debt effect requires unresolved company ids".to_string())
|
Err(company_target_import_error_message(target, company_context))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RuntimeEffect::SetCandidateAvailability { name, value } => {
|
RuntimeEffect::SetCandidateAvailability { name, value } => {
|
||||||
|
|
@ -765,7 +802,7 @@ fn smp_runtime_effect_to_runtime_effect(
|
||||||
RuntimeEffect::AppendEventRecord { record } => Ok(RuntimeEffect::AppendEventRecord {
|
RuntimeEffect::AppendEventRecord { record } => Ok(RuntimeEffect::AppendEventRecord {
|
||||||
record: Box::new(smp_runtime_record_template_to_runtime(
|
record: Box::new(smp_runtime_record_template_to_runtime(
|
||||||
record,
|
record,
|
||||||
known_company_ids,
|
company_context,
|
||||||
)?),
|
)?),
|
||||||
}),
|
}),
|
||||||
RuntimeEffect::ActivateEventRecord { record_id } => {
|
RuntimeEffect::ActivateEventRecord { record_id } => {
|
||||||
|
|
@ -786,7 +823,7 @@ fn smp_runtime_effect_to_runtime_effect(
|
||||||
|
|
||||||
fn smp_runtime_record_template_to_runtime(
|
fn smp_runtime_record_template_to_runtime(
|
||||||
record: &RuntimeEventRecordTemplate,
|
record: &RuntimeEventRecordTemplate,
|
||||||
known_company_ids: &BTreeSet<u32>,
|
company_context: &ImportCompanyContext,
|
||||||
) -> Result<RuntimeEventRecordTemplate, String> {
|
) -> Result<RuntimeEventRecordTemplate, String> {
|
||||||
Ok(RuntimeEventRecordTemplate {
|
Ok(RuntimeEventRecordTemplate {
|
||||||
record_id: record.record_id,
|
record_id: record.record_id,
|
||||||
|
|
@ -794,28 +831,71 @@ fn smp_runtime_record_template_to_runtime(
|
||||||
active: record.active,
|
active: record.active,
|
||||||
marks_collection_dirty: record.marks_collection_dirty,
|
marks_collection_dirty: record.marks_collection_dirty,
|
||||||
one_shot: record.one_shot,
|
one_shot: record.one_shot,
|
||||||
effects: smp_runtime_effects_to_runtime_effects(&record.effects, known_company_ids)?,
|
effects: smp_runtime_effects_to_runtime_effects(&record.effects, company_context)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn company_target_supported_for_import(
|
fn company_target_import_blocker(
|
||||||
target: &crate::RuntimeCompanyTarget,
|
target: &RuntimeCompanyTarget,
|
||||||
known_company_ids: &BTreeSet<u32>,
|
company_context: &ImportCompanyContext,
|
||||||
) -> bool {
|
) -> Option<CompanyTargetImportBlocker> {
|
||||||
match target {
|
match target {
|
||||||
crate::RuntimeCompanyTarget::AllActive => true,
|
RuntimeCompanyTarget::AllActive => None,
|
||||||
crate::RuntimeCompanyTarget::Ids { ids } => {
|
RuntimeCompanyTarget::Ids { ids } => {
|
||||||
!ids.is_empty()
|
if ids.is_empty()
|
||||||
&& ids
|
|| ids
|
||||||
.iter()
|
.iter()
|
||||||
.all(|company_id| known_company_ids.contains(company_id))
|
.any(|company_id| !company_context.known_company_ids.contains(company_id))
|
||||||
|
{
|
||||||
|
Some(CompanyTargetImportBlocker::MissingCompanyContext)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
RuntimeCompanyTarget::HumanCompanies | RuntimeCompanyTarget::AiCompanies => {
|
||||||
|
if !company_context.has_complete_controller_context {
|
||||||
|
Some(CompanyTargetImportBlocker::MissingCompanyRoleContext)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RuntimeCompanyTarget::SelectedCompany => {
|
||||||
|
if company_context.selected_company_id.is_some() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(CompanyTargetImportBlocker::MissingSelectionContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RuntimeCompanyTarget::ConditionTrueCompany => {
|
||||||
|
Some(CompanyTargetImportBlocker::MissingConditionContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn company_target_import_error_message(
|
||||||
|
target: &RuntimeCompanyTarget,
|
||||||
|
company_context: &ImportCompanyContext,
|
||||||
|
) -> String {
|
||||||
|
match company_target_import_blocker(target, company_context) {
|
||||||
|
Some(CompanyTargetImportBlocker::MissingCompanyContext) => {
|
||||||
|
"packed company effect requires resolved company ids".to_string()
|
||||||
|
}
|
||||||
|
Some(CompanyTargetImportBlocker::MissingSelectionContext) => {
|
||||||
|
"packed company effect requires selected_company_id context".to_string()
|
||||||
|
}
|
||||||
|
Some(CompanyTargetImportBlocker::MissingCompanyRoleContext) => {
|
||||||
|
"packed company effect requires company controller role context".to_string()
|
||||||
|
}
|
||||||
|
Some(CompanyTargetImportBlocker::MissingConditionContext) => {
|
||||||
|
"packed company effect requires condition-relative context".to_string()
|
||||||
|
}
|
||||||
|
None => "packed company effect is importable".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn determine_packed_event_import_outcome(
|
fn determine_packed_event_import_outcome(
|
||||||
record: &SmpLoadedPackedEventRecordSummary,
|
record: &SmpLoadedPackedEventRecordSummary,
|
||||||
known_company_ids: &BTreeSet<u32>,
|
company_context: &ImportCompanyContext,
|
||||||
imported: bool,
|
imported: bool,
|
||||||
) -> String {
|
) -> String {
|
||||||
if imported {
|
if imported {
|
||||||
|
|
@ -828,42 +908,105 @@ 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) = real_record_company_target_import_blocker(record, company_context) {
|
||||||
|
return company_target_import_outcome(blocker).to_string();
|
||||||
|
}
|
||||||
return "blocked_unmapped_real_descriptor".to_string();
|
return "blocked_unmapped_real_descriptor".to_string();
|
||||||
}
|
}
|
||||||
if packed_record_requires_missing_company_context(record, known_company_ids) {
|
if let Some(blocker) = packed_record_company_target_import_blocker(record, company_context) {
|
||||||
return "blocked_missing_company_context".to_string();
|
return company_target_import_outcome(blocker).to_string();
|
||||||
}
|
}
|
||||||
"blocked_unsupported_decode".to_string()
|
"blocked_unsupported_decode".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn packed_record_requires_missing_company_context(
|
fn packed_record_company_target_import_blocker(
|
||||||
record: &SmpLoadedPackedEventRecordSummary,
|
record: &SmpLoadedPackedEventRecordSummary,
|
||||||
known_company_ids: &BTreeSet<u32>,
|
company_context: &ImportCompanyContext,
|
||||||
) -> bool {
|
) -> Option<CompanyTargetImportBlocker> {
|
||||||
record
|
record
|
||||||
.decoded_actions
|
.decoded_actions
|
||||||
.iter()
|
.iter()
|
||||||
.any(|effect| runtime_effect_requires_missing_company_context(effect, known_company_ids))
|
.find_map(|effect| runtime_effect_company_target_import_blocker(effect, company_context))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn runtime_effect_requires_missing_company_context(
|
fn runtime_effect_company_target_import_blocker(
|
||||||
effect: &RuntimeEffect,
|
effect: &RuntimeEffect,
|
||||||
known_company_ids: &BTreeSet<u32>,
|
company_context: &ImportCompanyContext,
|
||||||
) -> bool {
|
) -> Option<CompanyTargetImportBlocker> {
|
||||||
match effect {
|
match effect {
|
||||||
RuntimeEffect::AdjustCompanyCash { target, .. }
|
RuntimeEffect::AdjustCompanyCash { target, .. }
|
||||||
| RuntimeEffect::AdjustCompanyDebt { target, .. } => {
|
| RuntimeEffect::AdjustCompanyDebt { target, .. } => {
|
||||||
!company_target_supported_for_import(target, known_company_ids)
|
company_target_import_blocker(target, company_context)
|
||||||
}
|
}
|
||||||
RuntimeEffect::AppendEventRecord { record } => record.effects.iter().any(|nested| {
|
RuntimeEffect::AppendEventRecord { record } => record
|
||||||
runtime_effect_requires_missing_company_context(nested, known_company_ids)
|
.effects
|
||||||
}),
|
.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 { .. }
|
||||||
| RuntimeEffect::ActivateEventRecord { .. }
|
| RuntimeEffect::ActivateEventRecord { .. }
|
||||||
| RuntimeEffect::DeactivateEventRecord { .. }
|
| RuntimeEffect::DeactivateEventRecord { .. }
|
||||||
| RuntimeEffect::RemoveEventRecord { .. } => false,
|
| RuntimeEffect::RemoveEventRecord { .. } => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn classify_real_grouped_company_targets(
|
||||||
|
record: &SmpLoadedPackedEventRecordSummary,
|
||||||
|
) -> Vec<Option<RuntimeCompanyTarget>> {
|
||||||
|
let Some(control) = &record.compact_control else {
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
|
||||||
|
control
|
||||||
|
.grouped_target_scope_ordinals_0x7fb
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(group_index, ordinal)| {
|
||||||
|
if !record
|
||||||
|
.grouped_effect_rows
|
||||||
|
.iter()
|
||||||
|
.any(|row| row.group_index == group_index)
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
classify_real_grouped_company_target(*ordinal)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn classify_real_grouped_company_target(ordinal: u8) -> Option<RuntimeCompanyTarget> {
|
||||||
|
match ordinal {
|
||||||
|
0 => Some(RuntimeCompanyTarget::ConditionTrueCompany),
|
||||||
|
1 => Some(RuntimeCompanyTarget::SelectedCompany),
|
||||||
|
2 => Some(RuntimeCompanyTarget::HumanCompanies),
|
||||||
|
3 => Some(RuntimeCompanyTarget::AiCompanies),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn real_record_company_target_import_blocker(
|
||||||
|
record: &SmpLoadedPackedEventRecordSummary,
|
||||||
|
company_context: &ImportCompanyContext,
|
||||||
|
) -> Option<CompanyTargetImportBlocker> {
|
||||||
|
classify_real_grouped_company_targets(record)
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.find_map(|target| company_target_import_blocker(&target, company_context))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn company_target_import_outcome(blocker: CompanyTargetImportBlocker) -> &'static str {
|
||||||
|
match blocker {
|
||||||
|
CompanyTargetImportBlocker::MissingCompanyContext => "blocked_missing_company_context",
|
||||||
|
CompanyTargetImportBlocker::MissingSelectionContext => {
|
||||||
|
"blocked_missing_selection_context"
|
||||||
|
}
|
||||||
|
CompanyTargetImportBlocker::MissingCompanyRoleContext => {
|
||||||
|
"blocked_missing_company_role_context"
|
||||||
|
}
|
||||||
|
CompanyTargetImportBlocker::MissingConditionContext => {
|
||||||
|
"blocked_missing_condition_context"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1154,6 +1297,7 @@ mod tests {
|
||||||
world_restore: RuntimeWorldRestoreState::default(),
|
world_restore: RuntimeWorldRestoreState::default(),
|
||||||
metadata: BTreeMap::new(),
|
metadata: BTreeMap::new(),
|
||||||
companies: Vec::new(),
|
companies: Vec::new(),
|
||||||
|
selected_company_id: None,
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
@ -1214,6 +1358,34 @@ mod tests {
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn synthetic_packed_record(
|
||||||
|
record_index: usize,
|
||||||
|
live_entry_id: u32,
|
||||||
|
effect: RuntimeEffect,
|
||||||
|
) -> crate::SmpLoadedPackedEventRecordSummary {
|
||||||
|
crate::SmpLoadedPackedEventRecordSummary {
|
||||||
|
record_index,
|
||||||
|
live_entry_id,
|
||||||
|
payload_offset: Some(0x7200 + (live_entry_id as usize * 0x20)),
|
||||||
|
payload_len: Some(64),
|
||||||
|
decode_status: "parity_only".to_string(),
|
||||||
|
payload_family: "synthetic_harness".to_string(),
|
||||||
|
trigger_kind: Some(7),
|
||||||
|
active: Some(true),
|
||||||
|
marks_collection_dirty: Some(false),
|
||||||
|
one_shot: Some(false),
|
||||||
|
compact_control: None,
|
||||||
|
text_bands: packed_text_bands(),
|
||||||
|
standalone_condition_row_count: 0,
|
||||||
|
standalone_condition_rows: vec![],
|
||||||
|
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||||
|
grouped_effect_rows: vec![],
|
||||||
|
decoded_actions: vec![effect],
|
||||||
|
executable_import_ready: false,
|
||||||
|
notes: vec!["synthetic test record".to_string()],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn real_grouped_rows() -> Vec<crate::SmpLoadedPackedEventGroupedEffectRowSummary> {
|
fn real_grouped_rows() -> Vec<crate::SmpLoadedPackedEventGroupedEffectRowSummary> {
|
||||||
vec![crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
vec![crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||||
group_index: 0,
|
group_index: 0,
|
||||||
|
|
@ -1248,6 +1420,22 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn real_compact_control_without_symbolic_company_scope(
|
||||||
|
) -> crate::SmpLoadedPackedEventCompactControlSummary {
|
||||||
|
crate::SmpLoadedPackedEventCompactControlSummary {
|
||||||
|
mode_byte_0x7ef: 6,
|
||||||
|
primary_selector_0x7f0: 0x63,
|
||||||
|
grouped_mode_0x7f4: 2,
|
||||||
|
one_shot_header_0x7f5: 1,
|
||||||
|
modifier_flag_0x7f9: 1,
|
||||||
|
modifier_flag_0x7fa: 0,
|
||||||
|
grouped_target_scope_ordinals_0x7fb: vec![8, 9, 10, 11],
|
||||||
|
grouped_scope_checkboxes_0x7ff: vec![1, 0, 1, 0],
|
||||||
|
summary_toggle_0x800: 1,
|
||||||
|
grouped_territory_selectors_0x80f: vec![-1, 10, -1, 22],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn loads_dump_document() {
|
fn loads_dump_document() {
|
||||||
let text = serde_json::to_string(&RuntimeStateDumpDocument {
|
let text = serde_json::to_string(&RuntimeStateDumpDocument {
|
||||||
|
|
@ -1883,6 +2071,190 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn classifies_symbolic_company_target_blockers_for_standalone_import() {
|
||||||
|
let save_slice = SmpLoadedSaveSlice {
|
||||||
|
file_extension_hint: Some("gms".to_string()),
|
||||||
|
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
|
||||||
|
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
||||||
|
mechanism_confidence: "grounded".to_string(),
|
||||||
|
trailer_family: None,
|
||||||
|
bridge_family: None,
|
||||||
|
profile: None,
|
||||||
|
candidate_availability_table: None,
|
||||||
|
special_conditions_table: None,
|
||||||
|
event_runtime_collection: Some(crate::SmpLoadedEventRuntimeCollectionSummary {
|
||||||
|
source_kind: "packed-event-runtime-collection".to_string(),
|
||||||
|
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
||||||
|
mechanism_confidence: "grounded".to_string(),
|
||||||
|
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
|
||||||
|
metadata_tag_offset: 0x7100,
|
||||||
|
records_tag_offset: 0x7200,
|
||||||
|
close_tag_offset: 0x7600,
|
||||||
|
packed_state_version: 0x3e9,
|
||||||
|
packed_state_version_hex: "0x000003e9".to_string(),
|
||||||
|
live_id_bound: 12,
|
||||||
|
live_record_count: 3,
|
||||||
|
live_entry_ids: vec![10, 11, 12],
|
||||||
|
decoded_record_count: 3,
|
||||||
|
imported_runtime_record_count: 0,
|
||||||
|
records: vec![
|
||||||
|
synthetic_packed_record(
|
||||||
|
0,
|
||||||
|
10,
|
||||||
|
RuntimeEffect::AdjustCompanyCash {
|
||||||
|
target: RuntimeCompanyTarget::SelectedCompany,
|
||||||
|
delta: 1,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
synthetic_packed_record(
|
||||||
|
1,
|
||||||
|
11,
|
||||||
|
RuntimeEffect::AdjustCompanyDebt {
|
||||||
|
target: RuntimeCompanyTarget::HumanCompanies,
|
||||||
|
delta: 2,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
synthetic_packed_record(
|
||||||
|
2,
|
||||||
|
12,
|
||||||
|
RuntimeEffect::AdjustCompanyDebt {
|
||||||
|
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||||
|
delta: 3,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
notes: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let import = project_save_slice_to_runtime_state_import(
|
||||||
|
&save_slice,
|
||||||
|
"symbolic-blockers",
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.expect("standalone projection should succeed");
|
||||||
|
|
||||||
|
assert!(import.state.event_runtime_records.is_empty());
|
||||||
|
let outcomes = import
|
||||||
|
.state
|
||||||
|
.packed_event_collection
|
||||||
|
.as_ref()
|
||||||
|
.expect("packed event collection should be present")
|
||||||
|
.records
|
||||||
|
.iter()
|
||||||
|
.map(|record| record.import_outcome.clone())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
assert_eq!(
|
||||||
|
outcomes,
|
||||||
|
vec![
|
||||||
|
Some("blocked_missing_selection_context".to_string()),
|
||||||
|
Some("blocked_missing_company_role_context".to_string()),
|
||||||
|
Some("blocked_missing_condition_context".to_string()),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn overlays_symbolic_company_targets_into_executable_runtime_records() {
|
||||||
|
let base_state = RuntimeState {
|
||||||
|
companies: vec![
|
||||||
|
crate::RuntimeCompany {
|
||||||
|
company_id: 1,
|
||||||
|
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||||
|
current_cash: 100,
|
||||||
|
debt: 10,
|
||||||
|
},
|
||||||
|
crate::RuntimeCompany {
|
||||||
|
company_id: 2,
|
||||||
|
controller_kind: RuntimeCompanyControllerKind::Ai,
|
||||||
|
current_cash: 50,
|
||||||
|
debt: 20,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
selected_company_id: Some(1),
|
||||||
|
..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: 2,
|
||||||
|
live_entry_ids: vec![21, 22],
|
||||||
|
decoded_record_count: 2,
|
||||||
|
imported_runtime_record_count: 0,
|
||||||
|
records: vec![
|
||||||
|
synthetic_packed_record(
|
||||||
|
0,
|
||||||
|
21,
|
||||||
|
RuntimeEffect::AdjustCompanyCash {
|
||||||
|
target: RuntimeCompanyTarget::SelectedCompany,
|
||||||
|
delta: 15,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
synthetic_packed_record(
|
||||||
|
1,
|
||||||
|
22,
|
||||||
|
RuntimeEffect::AdjustCompanyDebt {
|
||||||
|
target: RuntimeCompanyTarget::AiCompanies,
|
||||||
|
delta: 4,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
notes: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut import = project_save_slice_overlay_to_runtime_state_import(
|
||||||
|
&base_state,
|
||||||
|
&save_slice,
|
||||||
|
"symbolic-overlay",
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.expect("overlay projection should succeed");
|
||||||
|
|
||||||
|
assert_eq!(import.state.event_runtime_records.len(), 2);
|
||||||
|
let outcomes = import
|
||||||
|
.state
|
||||||
|
.packed_event_collection
|
||||||
|
.as_ref()
|
||||||
|
.expect("packed event collection should be present")
|
||||||
|
.records
|
||||||
|
.iter()
|
||||||
|
.map(|record| record.import_outcome.clone())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
assert_eq!(
|
||||||
|
outcomes,
|
||||||
|
vec![Some("imported".to_string()), Some("imported".to_string())]
|
||||||
|
);
|
||||||
|
|
||||||
|
execute_step_command(
|
||||||
|
&mut import.state,
|
||||||
|
&StepCommand::ServiceTriggerKind { trigger_kind: 7 },
|
||||||
|
)
|
||||||
|
.expect("symbolic overlay dispatch should succeed");
|
||||||
|
|
||||||
|
assert_eq!(import.state.companies[0].current_cash, 115);
|
||||||
|
assert_eq!(import.state.companies[1].debt, 24);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn leaves_real_records_without_compact_control_blocked_missing_compact_control() {
|
fn leaves_real_records_without_compact_control_blocked_missing_compact_control() {
|
||||||
let save_slice = SmpLoadedSaveSlice {
|
let save_slice = SmpLoadedSaveSlice {
|
||||||
|
|
@ -1970,7 +2342,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn leaves_real_records_with_compact_control_blocked_unmapped_real_descriptor() {
|
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()),
|
||||||
|
|
@ -2038,6 +2410,76 @@ mod tests {
|
||||||
.map(|control| control.mode_byte_0x7ef),
|
.map(|control| control.mode_byte_0x7ef),
|
||||||
Some(6)
|
Some(6)
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
import
|
||||||
|
.state
|
||||||
|
.packed_event_collection
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|summary| summary.records[0].import_outcome.as_deref()),
|
||||||
|
Some("blocked_missing_condition_context")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn leaves_real_records_with_unclassified_scope_blocked_unmapped_real_descriptor() {
|
||||||
|
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: 7,
|
||||||
|
live_record_count: 1,
|
||||||
|
live_entry_ids: vec![7],
|
||||||
|
decoded_record_count: 1,
|
||||||
|
imported_runtime_record_count: 0,
|
||||||
|
records: vec![crate::SmpLoadedPackedEventRecordSummary {
|
||||||
|
record_index: 0,
|
||||||
|
live_entry_id: 7,
|
||||||
|
payload_offset: Some(0x7202),
|
||||||
|
payload_len: Some(133),
|
||||||
|
decode_status: "parity_only".to_string(),
|
||||||
|
payload_family: "real_packed_v1".to_string(),
|
||||||
|
trigger_kind: Some(6),
|
||||||
|
active: None,
|
||||||
|
marks_collection_dirty: None,
|
||||||
|
one_shot: Some(true),
|
||||||
|
compact_control: Some(real_compact_control_without_symbolic_company_scope()),
|
||||||
|
text_bands: packed_text_bands(),
|
||||||
|
standalone_condition_row_count: 1,
|
||||||
|
standalone_condition_rows: real_condition_rows(),
|
||||||
|
grouped_effect_row_counts: vec![1, 0, 0, 0],
|
||||||
|
grouped_effect_rows: real_grouped_rows(),
|
||||||
|
decoded_actions: vec![],
|
||||||
|
executable_import_ready: false,
|
||||||
|
notes: vec!["decoded from grounded real 0x4e9a row framing".to_string()],
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
notes: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
let import = project_save_slice_to_runtime_state_import(
|
||||||
|
&save_slice,
|
||||||
|
"packed-events-real-descriptor-frontier",
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.expect("save slice should project");
|
||||||
|
|
||||||
|
assert!(import.state.event_runtime_records.is_empty());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
import
|
import
|
||||||
.state
|
.state
|
||||||
|
|
@ -2063,9 +2505,11 @@ mod tests {
|
||||||
metadata: BTreeMap::from([("base.note".to_string(), "kept".to_string())]),
|
metadata: BTreeMap::from([("base.note".to_string(), "kept".to_string())]),
|
||||||
companies: vec![crate::RuntimeCompany {
|
companies: vec![crate::RuntimeCompany {
|
||||||
company_id: 42,
|
company_id: 42,
|
||||||
|
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||||
current_cash: 500,
|
current_cash: 500,
|
||||||
debt: 20,
|
debt: 20,
|
||||||
}],
|
}],
|
||||||
|
selected_company_id: Some(42),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: vec![RuntimeEventRecord {
|
event_runtime_records: vec![RuntimeEventRecord {
|
||||||
record_id: 1,
|
record_id: 1,
|
||||||
|
|
@ -2223,9 +2667,11 @@ mod tests {
|
||||||
metadata: BTreeMap::new(),
|
metadata: BTreeMap::new(),
|
||||||
companies: vec![crate::RuntimeCompany {
|
companies: vec![crate::RuntimeCompany {
|
||||||
company_id: 42,
|
company_id: 42,
|
||||||
|
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||||
current_cash: 100,
|
current_cash: 100,
|
||||||
debt: 0,
|
debt: 0,
|
||||||
}],
|
}],
|
||||||
|
selected_company_id: Some(42),
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ pub use pk4::{
|
||||||
extract_pk4_entry_bytes, extract_pk4_entry_file, inspect_pk4_bytes, inspect_pk4_file,
|
extract_pk4_entry_bytes, extract_pk4_entry_file, inspect_pk4_bytes, inspect_pk4_file,
|
||||||
};
|
};
|
||||||
pub use runtime::{
|
pub use runtime::{
|
||||||
RuntimeCompany, RuntimeCompanyTarget, RuntimeEffect, RuntimeEventRecord,
|
RuntimeCompany, RuntimeCompanyControllerKind, RuntimeCompanyTarget, RuntimeEffect, RuntimeEventRecord,
|
||||||
RuntimeEventRecordTemplate, RuntimePackedEventCollectionSummary,
|
RuntimeEventRecordTemplate, RuntimePackedEventCollectionSummary,
|
||||||
RuntimePackedEventCompactControlSummary,
|
RuntimePackedEventCompactControlSummary,
|
||||||
RuntimePackedEventConditionRowSummary, RuntimePackedEventGroupedEffectRowSummary,
|
RuntimePackedEventConditionRowSummary, RuntimePackedEventGroupedEffectRowSummary,
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,7 @@ mod tests {
|
||||||
world_restore: RuntimeWorldRestoreState::default(),
|
world_restore: RuntimeWorldRestoreState::default(),
|
||||||
metadata: BTreeMap::new(),
|
metadata: BTreeMap::new(),
|
||||||
companies: Vec::new(),
|
companies: Vec::new(),
|
||||||
|
selected_company_id: None,
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
|
||||||
|
|
@ -4,17 +4,32 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::CalendarPoint;
|
use crate::CalendarPoint;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum RuntimeCompanyControllerKind {
|
||||||
|
#[default]
|
||||||
|
Unknown,
|
||||||
|
Human,
|
||||||
|
Ai,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct RuntimeCompany {
|
pub struct RuntimeCompany {
|
||||||
pub company_id: u32,
|
pub company_id: u32,
|
||||||
pub current_cash: i64,
|
pub current_cash: i64,
|
||||||
pub debt: u64,
|
pub debt: u64,
|
||||||
|
#[serde(default)]
|
||||||
|
pub controller_kind: RuntimeCompanyControllerKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||||
pub enum RuntimeCompanyTarget {
|
pub enum RuntimeCompanyTarget {
|
||||||
AllActive,
|
AllActive,
|
||||||
|
HumanCompanies,
|
||||||
|
AiCompanies,
|
||||||
|
SelectedCompany,
|
||||||
|
ConditionTrueCompany,
|
||||||
Ids { ids: Vec<u32> },
|
Ids { ids: Vec<u32> },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -137,6 +152,8 @@ pub struct RuntimePackedEventRecordSummary {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub grouped_effect_rows: Vec<RuntimePackedEventGroupedEffectRowSummary>,
|
pub grouped_effect_rows: Vec<RuntimePackedEventGroupedEffectRowSummary>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub grouped_company_targets: Vec<Option<RuntimeCompanyTarget>>,
|
||||||
|
#[serde(default)]
|
||||||
pub decoded_actions: Vec<RuntimeEffect>,
|
pub decoded_actions: Vec<RuntimeEffect>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub executable_import_ready: bool,
|
pub executable_import_ready: bool,
|
||||||
|
|
@ -303,6 +320,8 @@ pub struct RuntimeState {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub companies: Vec<RuntimeCompany>,
|
pub companies: Vec<RuntimeCompany>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub selected_company_id: Option<u32>,
|
||||||
|
#[serde(default)]
|
||||||
pub packed_event_collection: Option<RuntimePackedEventCollectionSummary>,
|
pub packed_event_collection: Option<RuntimePackedEventCollectionSummary>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub event_runtime_records: Vec<RuntimeEventRecord>,
|
pub event_runtime_records: Vec<RuntimeEventRecord>,
|
||||||
|
|
@ -324,6 +343,14 @@ impl RuntimeState {
|
||||||
return Err(format!("duplicate company_id {}", company.company_id));
|
return Err(format!("duplicate company_id {}", company.company_id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(selected_company_id) = self.selected_company_id {
|
||||||
|
if !seen_company_ids.contains(&selected_company_id) {
|
||||||
|
return Err(format!(
|
||||||
|
"selected_company_id {} does not reference a live company",
|
||||||
|
selected_company_id
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut seen_record_ids = BTreeSet::new();
|
let mut seen_record_ids = BTreeSet::new();
|
||||||
for record in &self.event_runtime_records {
|
for record in &self.event_runtime_records {
|
||||||
|
|
@ -672,7 +699,11 @@ fn validate_company_target(
|
||||||
valid_company_ids: &BTreeSet<u32>,
|
valid_company_ids: &BTreeSet<u32>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
match target {
|
match target {
|
||||||
RuntimeCompanyTarget::AllActive => Ok(()),
|
RuntimeCompanyTarget::AllActive
|
||||||
|
| RuntimeCompanyTarget::HumanCompanies
|
||||||
|
| RuntimeCompanyTarget::AiCompanies
|
||||||
|
| RuntimeCompanyTarget::SelectedCompany
|
||||||
|
| RuntimeCompanyTarget::ConditionTrueCompany => Ok(()),
|
||||||
RuntimeCompanyTarget::Ids { ids } => {
|
RuntimeCompanyTarget::Ids { ids } => {
|
||||||
if ids.is_empty() {
|
if ids.is_empty() {
|
||||||
return Err("target ids must not be empty".to_string());
|
return Err("target ids must not be empty".to_string());
|
||||||
|
|
@ -709,13 +740,16 @@ mod tests {
|
||||||
company_id: 1,
|
company_id: 1,
|
||||||
current_cash: 100,
|
current_cash: 100,
|
||||||
debt: 0,
|
debt: 0,
|
||||||
|
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||||
},
|
},
|
||||||
RuntimeCompany {
|
RuntimeCompany {
|
||||||
company_id: 1,
|
company_id: 1,
|
||||||
current_cash: 200,
|
current_cash: 200,
|
||||||
debt: 0,
|
debt: 0,
|
||||||
|
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
selected_company_id: None,
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
@ -762,6 +796,7 @@ mod tests {
|
||||||
},
|
},
|
||||||
metadata: BTreeMap::new(),
|
metadata: BTreeMap::new(),
|
||||||
companies: Vec::new(),
|
companies: Vec::new(),
|
||||||
|
selected_company_id: None,
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
@ -789,7 +824,9 @@ mod tests {
|
||||||
company_id: 1,
|
company_id: 1,
|
||||||
current_cash: 100,
|
current_cash: 100,
|
||||||
debt: 0,
|
debt: 0,
|
||||||
|
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||||
}],
|
}],
|
||||||
|
selected_company_id: None,
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: vec![RuntimeEventRecord {
|
event_runtime_records: vec![RuntimeEventRecord {
|
||||||
record_id: 7,
|
record_id: 7,
|
||||||
|
|
@ -829,7 +866,9 @@ mod tests {
|
||||||
company_id: 1,
|
company_id: 1,
|
||||||
current_cash: 100,
|
current_cash: 100,
|
||||||
debt: 0,
|
debt: 0,
|
||||||
|
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||||
}],
|
}],
|
||||||
|
selected_company_id: None,
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: vec![RuntimeEventRecord {
|
event_runtime_records: vec![RuntimeEventRecord {
|
||||||
record_id: 7,
|
record_id: 7,
|
||||||
|
|
@ -875,6 +914,7 @@ mod tests {
|
||||||
world_restore: RuntimeWorldRestoreState::default(),
|
world_restore: RuntimeWorldRestoreState::default(),
|
||||||
metadata: BTreeMap::new(),
|
metadata: BTreeMap::new(),
|
||||||
companies: Vec::new(),
|
companies: Vec::new(),
|
||||||
|
selected_company_id: None,
|
||||||
packed_event_collection: Some(RuntimePackedEventCollectionSummary {
|
packed_event_collection: Some(RuntimePackedEventCollectionSummary {
|
||||||
source_kind: "packed-event-runtime-collection".to_string(),
|
source_kind: "packed-event-runtime-collection".to_string(),
|
||||||
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
||||||
|
|
@ -905,6 +945,7 @@ mod tests {
|
||||||
standalone_condition_rows: Vec::new(),
|
standalone_condition_rows: Vec::new(),
|
||||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||||
grouped_effect_rows: Vec::new(),
|
grouped_effect_rows: Vec::new(),
|
||||||
|
grouped_company_targets: Vec::new(),
|
||||||
decoded_actions: Vec::new(),
|
decoded_actions: Vec::new(),
|
||||||
executable_import_ready: false,
|
executable_import_ready: false,
|
||||||
import_outcome: None,
|
import_outcome: None,
|
||||||
|
|
@ -927,6 +968,7 @@ mod tests {
|
||||||
standalone_condition_rows: Vec::new(),
|
standalone_condition_rows: Vec::new(),
|
||||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||||
grouped_effect_rows: Vec::new(),
|
grouped_effect_rows: Vec::new(),
|
||||||
|
grouped_company_targets: Vec::new(),
|
||||||
decoded_actions: Vec::new(),
|
decoded_actions: Vec::new(),
|
||||||
executable_import_ready: false,
|
executable_import_ready: false,
|
||||||
import_outcome: None,
|
import_outcome: None,
|
||||||
|
|
@ -942,4 +984,34 @@ mod tests {
|
||||||
|
|
||||||
assert!(state.validate().is_err());
|
assert!(state.validate().is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_selected_company_id_that_does_not_exist() {
|
||||||
|
let state = RuntimeState {
|
||||||
|
calendar: CalendarPoint {
|
||||||
|
year: 1830,
|
||||||
|
month_slot: 0,
|
||||||
|
phase_slot: 0,
|
||||||
|
tick_slot: 0,
|
||||||
|
},
|
||||||
|
world_flags: BTreeMap::new(),
|
||||||
|
save_profile: RuntimeSaveProfileState::default(),
|
||||||
|
world_restore: RuntimeWorldRestoreState::default(),
|
||||||
|
metadata: BTreeMap::new(),
|
||||||
|
companies: vec![RuntimeCompany {
|
||||||
|
company_id: 1,
|
||||||
|
current_cash: 100,
|
||||||
|
debt: 0,
|
||||||
|
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||||
|
}],
|
||||||
|
selected_company_id: Some(2),
|
||||||
|
packed_event_collection: None,
|
||||||
|
event_runtime_records: Vec::new(),
|
||||||
|
candidate_availability: BTreeMap::new(),
|
||||||
|
special_conditions: BTreeMap::new(),
|
||||||
|
service_state: RuntimeServiceState::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(state.validate().is_err());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,8 @@ use std::collections::BTreeSet;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
RuntimeCompanyTarget, RuntimeEffect, RuntimeEventRecordTemplate, RuntimeState, RuntimeSummary,
|
RuntimeCompanyControllerKind, RuntimeCompanyTarget, RuntimeEffect, RuntimeEventRecordTemplate,
|
||||||
|
RuntimeState, RuntimeSummary,
|
||||||
calendar::BoundaryEventKind,
|
calendar::BoundaryEventKind,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -430,6 +431,49 @@ fn resolve_company_target_ids(
|
||||||
}
|
}
|
||||||
Ok(ids.clone())
|
Ok(ids.clone())
|
||||||
}
|
}
|
||||||
|
RuntimeCompanyTarget::HumanCompanies => {
|
||||||
|
if state
|
||||||
|
.companies
|
||||||
|
.iter()
|
||||||
|
.any(|company| company.controller_kind == RuntimeCompanyControllerKind::Unknown)
|
||||||
|
{
|
||||||
|
return Err(
|
||||||
|
"target requires company role context but at least one company has unknown controller_kind"
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(state
|
||||||
|
.companies
|
||||||
|
.iter()
|
||||||
|
.filter(|company| company.controller_kind == RuntimeCompanyControllerKind::Human)
|
||||||
|
.map(|company| company.company_id)
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
RuntimeCompanyTarget::AiCompanies => {
|
||||||
|
if state
|
||||||
|
.companies
|
||||||
|
.iter()
|
||||||
|
.any(|company| company.controller_kind == RuntimeCompanyControllerKind::Unknown)
|
||||||
|
{
|
||||||
|
return Err(
|
||||||
|
"target requires company role context but at least one company has unknown controller_kind"
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(state
|
||||||
|
.companies
|
||||||
|
.iter()
|
||||||
|
.filter(|company| company.controller_kind == RuntimeCompanyControllerKind::Ai)
|
||||||
|
.map(|company| company.company_id)
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
RuntimeCompanyTarget::SelectedCompany => state
|
||||||
|
.selected_company_id
|
||||||
|
.map(|company_id| vec![company_id])
|
||||||
|
.ok_or_else(|| "target requires selected_company_id context".to_string()),
|
||||||
|
RuntimeCompanyTarget::ConditionTrueCompany => {
|
||||||
|
Err("target requires condition-evaluation context".to_string())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -451,9 +495,9 @@ mod tests {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
CalendarPoint, RuntimeCompany, RuntimeCompanyTarget, RuntimeEffect, RuntimeEventRecord,
|
CalendarPoint, RuntimeCompany, RuntimeCompanyControllerKind, RuntimeCompanyTarget,
|
||||||
RuntimeEventRecordTemplate, RuntimeSaveProfileState, RuntimeServiceState,
|
RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate, RuntimeSaveProfileState,
|
||||||
RuntimeWorldRestoreState,
|
RuntimeServiceState, RuntimeWorldRestoreState,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn state() -> RuntimeState {
|
fn state() -> RuntimeState {
|
||||||
|
|
@ -470,9 +514,11 @@ mod tests {
|
||||||
metadata: BTreeMap::new(),
|
metadata: BTreeMap::new(),
|
||||||
companies: vec![RuntimeCompany {
|
companies: vec![RuntimeCompany {
|
||||||
company_id: 1,
|
company_id: 1,
|
||||||
|
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||||
current_cash: 10,
|
current_cash: 10,
|
||||||
debt: 0,
|
debt: 0,
|
||||||
}],
|
}],
|
||||||
|
selected_company_id: None,
|
||||||
packed_event_collection: None,
|
packed_event_collection: None,
|
||||||
event_runtime_records: Vec::new(),
|
event_runtime_records: Vec::new(),
|
||||||
candidate_availability: BTreeMap::new(),
|
candidate_availability: BTreeMap::new(),
|
||||||
|
|
@ -630,11 +676,13 @@ mod tests {
|
||||||
companies: vec![
|
companies: vec![
|
||||||
RuntimeCompany {
|
RuntimeCompany {
|
||||||
company_id: 1,
|
company_id: 1,
|
||||||
|
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||||
current_cash: 10,
|
current_cash: 10,
|
||||||
debt: 5,
|
debt: 5,
|
||||||
},
|
},
|
||||||
RuntimeCompany {
|
RuntimeCompany {
|
||||||
company_id: 2,
|
company_id: 2,
|
||||||
|
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||||
current_cash: 20,
|
current_cash: 20,
|
||||||
debt: 8,
|
debt: 8,
|
||||||
},
|
},
|
||||||
|
|
@ -674,6 +722,165 @@ mod tests {
|
||||||
assert_eq!(result.service_events[0].mutated_company_ids, vec![2]);
|
assert_eq!(result.service_events[0].mutated_company_ids, vec![2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resolves_symbolic_company_targets() {
|
||||||
|
let mut state = RuntimeState {
|
||||||
|
companies: vec![
|
||||||
|
RuntimeCompany {
|
||||||
|
company_id: 1,
|
||||||
|
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||||
|
current_cash: 10,
|
||||||
|
debt: 0,
|
||||||
|
},
|
||||||
|
RuntimeCompany {
|
||||||
|
company_id: 2,
|
||||||
|
controller_kind: RuntimeCompanyControllerKind::Ai,
|
||||||
|
current_cash: 20,
|
||||||
|
debt: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
selected_company_id: Some(1),
|
||||||
|
event_runtime_records: vec![
|
||||||
|
RuntimeEventRecord {
|
||||||
|
record_id: 11,
|
||||||
|
trigger_kind: 7,
|
||||||
|
active: true,
|
||||||
|
service_count: 0,
|
||||||
|
marks_collection_dirty: false,
|
||||||
|
one_shot: false,
|
||||||
|
has_fired: false,
|
||||||
|
effects: vec![RuntimeEffect::AdjustCompanyCash {
|
||||||
|
target: RuntimeCompanyTarget::HumanCompanies,
|
||||||
|
delta: 5,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
RuntimeEventRecord {
|
||||||
|
record_id: 12,
|
||||||
|
trigger_kind: 7,
|
||||||
|
active: true,
|
||||||
|
service_count: 0,
|
||||||
|
marks_collection_dirty: false,
|
||||||
|
one_shot: false,
|
||||||
|
has_fired: false,
|
||||||
|
effects: vec![RuntimeEffect::AdjustCompanyDebt {
|
||||||
|
target: RuntimeCompanyTarget::AiCompanies,
|
||||||
|
delta: 3,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
RuntimeEventRecord {
|
||||||
|
record_id: 13,
|
||||||
|
trigger_kind: 7,
|
||||||
|
active: true,
|
||||||
|
service_count: 0,
|
||||||
|
marks_collection_dirty: false,
|
||||||
|
one_shot: false,
|
||||||
|
has_fired: false,
|
||||||
|
effects: vec![RuntimeEffect::AdjustCompanyCash {
|
||||||
|
target: RuntimeCompanyTarget::SelectedCompany,
|
||||||
|
delta: 7,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
..state()
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = execute_step_command(
|
||||||
|
&mut state,
|
||||||
|
&StepCommand::ServiceTriggerKind { trigger_kind: 7 },
|
||||||
|
)
|
||||||
|
.expect("symbolic target effects should succeed");
|
||||||
|
|
||||||
|
assert_eq!(state.companies[0].current_cash, 22);
|
||||||
|
assert_eq!(state.companies[0].debt, 0);
|
||||||
|
assert_eq!(state.companies[1].current_cash, 20);
|
||||||
|
assert_eq!(state.companies[1].debt, 5);
|
||||||
|
assert_eq!(result.service_events[0].mutated_company_ids, vec![1, 2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_selected_company_target_without_selection_context() {
|
||||||
|
let mut state = RuntimeState {
|
||||||
|
event_runtime_records: vec![RuntimeEventRecord {
|
||||||
|
record_id: 14,
|
||||||
|
trigger_kind: 7,
|
||||||
|
active: true,
|
||||||
|
service_count: 0,
|
||||||
|
marks_collection_dirty: false,
|
||||||
|
one_shot: false,
|
||||||
|
has_fired: false,
|
||||||
|
effects: vec![RuntimeEffect::AdjustCompanyCash {
|
||||||
|
target: RuntimeCompanyTarget::SelectedCompany,
|
||||||
|
delta: 1,
|
||||||
|
}],
|
||||||
|
}],
|
||||||
|
..state()
|
||||||
|
};
|
||||||
|
|
||||||
|
let error = execute_step_command(
|
||||||
|
&mut state,
|
||||||
|
&StepCommand::ServiceTriggerKind { trigger_kind: 7 },
|
||||||
|
)
|
||||||
|
.expect_err("selected company target should require selection context");
|
||||||
|
|
||||||
|
assert!(error.contains("selected_company_id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_human_or_ai_targets_without_role_context() {
|
||||||
|
let mut state = RuntimeState {
|
||||||
|
event_runtime_records: vec![RuntimeEventRecord {
|
||||||
|
record_id: 15,
|
||||||
|
trigger_kind: 7,
|
||||||
|
active: true,
|
||||||
|
service_count: 0,
|
||||||
|
marks_collection_dirty: false,
|
||||||
|
one_shot: false,
|
||||||
|
has_fired: false,
|
||||||
|
effects: vec![RuntimeEffect::AdjustCompanyCash {
|
||||||
|
target: RuntimeCompanyTarget::HumanCompanies,
|
||||||
|
delta: 1,
|
||||||
|
}],
|
||||||
|
}],
|
||||||
|
..state()
|
||||||
|
};
|
||||||
|
|
||||||
|
let error = execute_step_command(
|
||||||
|
&mut state,
|
||||||
|
&StepCommand::ServiceTriggerKind { trigger_kind: 7 },
|
||||||
|
)
|
||||||
|
.expect_err("human target should require controller metadata");
|
||||||
|
|
||||||
|
assert!(error.contains("controller_kind"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_condition_true_company_target_without_condition_context() {
|
||||||
|
let mut state = RuntimeState {
|
||||||
|
event_runtime_records: vec![RuntimeEventRecord {
|
||||||
|
record_id: 16,
|
||||||
|
trigger_kind: 7,
|
||||||
|
active: true,
|
||||||
|
service_count: 0,
|
||||||
|
marks_collection_dirty: false,
|
||||||
|
one_shot: false,
|
||||||
|
has_fired: false,
|
||||||
|
effects: vec![RuntimeEffect::AdjustCompanyCash {
|
||||||
|
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||||
|
delta: 1,
|
||||||
|
}],
|
||||||
|
}],
|
||||||
|
..state()
|
||||||
|
};
|
||||||
|
|
||||||
|
let error = execute_step_command(
|
||||||
|
&mut state,
|
||||||
|
&StepCommand::ServiceTriggerKind { trigger_kind: 7 },
|
||||||
|
)
|
||||||
|
.expect_err("condition-relative target should remain blocked");
|
||||||
|
|
||||||
|
assert!(error.contains("condition-evaluation context"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn one_shot_record_only_fires_once() {
|
fn one_shot_record_only_fires_once() {
|
||||||
let mut state = RuntimeState {
|
let mut state = RuntimeState {
|
||||||
|
|
@ -718,6 +925,7 @@ mod tests {
|
||||||
let mut state = RuntimeState {
|
let mut state = RuntimeState {
|
||||||
companies: vec![RuntimeCompany {
|
companies: vec![RuntimeCompany {
|
||||||
company_id: 1,
|
company_id: 1,
|
||||||
|
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||||
current_cash: 10,
|
current_cash: 10,
|
||||||
debt: 2,
|
debt: 2,
|
||||||
}],
|
}],
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,9 @@ pub struct RuntimeSummary {
|
||||||
pub packed_event_parity_only_record_count: usize,
|
pub packed_event_parity_only_record_count: usize,
|
||||||
pub packed_event_unsupported_record_count: usize,
|
pub packed_event_unsupported_record_count: usize,
|
||||||
pub packed_event_blocked_missing_company_context_count: usize,
|
pub packed_event_blocked_missing_company_context_count: usize,
|
||||||
|
pub packed_event_blocked_missing_selection_context_count: usize,
|
||||||
|
pub packed_event_blocked_missing_company_role_context_count: usize,
|
||||||
|
pub packed_event_blocked_missing_condition_context_count: usize,
|
||||||
pub packed_event_blocked_missing_compact_control_count: usize,
|
pub packed_event_blocked_missing_compact_control_count: usize,
|
||||||
pub packed_event_blocked_unmapped_real_descriptor_count: usize,
|
pub packed_event_blocked_unmapped_real_descriptor_count: usize,
|
||||||
pub packed_event_blocked_structural_only_count: usize,
|
pub packed_event_blocked_structural_only_count: usize,
|
||||||
|
|
@ -171,6 +174,48 @@ impl RuntimeSummary {
|
||||||
.count()
|
.count()
|
||||||
})
|
})
|
||||||
.unwrap_or(0),
|
.unwrap_or(0),
|
||||||
|
packed_event_blocked_missing_selection_context_count: state
|
||||||
|
.packed_event_collection
|
||||||
|
.as_ref()
|
||||||
|
.map(|summary| {
|
||||||
|
summary
|
||||||
|
.records
|
||||||
|
.iter()
|
||||||
|
.filter(|record| {
|
||||||
|
record.import_outcome.as_deref()
|
||||||
|
== Some("blocked_missing_selection_context")
|
||||||
|
})
|
||||||
|
.count()
|
||||||
|
})
|
||||||
|
.unwrap_or(0),
|
||||||
|
packed_event_blocked_missing_company_role_context_count: state
|
||||||
|
.packed_event_collection
|
||||||
|
.as_ref()
|
||||||
|
.map(|summary| {
|
||||||
|
summary
|
||||||
|
.records
|
||||||
|
.iter()
|
||||||
|
.filter(|record| {
|
||||||
|
record.import_outcome.as_deref()
|
||||||
|
== Some("blocked_missing_company_role_context")
|
||||||
|
})
|
||||||
|
.count()
|
||||||
|
})
|
||||||
|
.unwrap_or(0),
|
||||||
|
packed_event_blocked_missing_condition_context_count: state
|
||||||
|
.packed_event_collection
|
||||||
|
.as_ref()
|
||||||
|
.map(|summary| {
|
||||||
|
summary
|
||||||
|
.records
|
||||||
|
.iter()
|
||||||
|
.filter(|record| {
|
||||||
|
record.import_outcome.as_deref()
|
||||||
|
== Some("blocked_missing_condition_context")
|
||||||
|
})
|
||||||
|
.count()
|
||||||
|
})
|
||||||
|
.unwrap_or(0),
|
||||||
packed_event_blocked_missing_compact_control_count: state
|
packed_event_blocked_missing_compact_control_count: state
|
||||||
.packed_event_collection
|
.packed_event_collection
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
@ -277,6 +322,7 @@ mod tests {
|
||||||
world_restore: RuntimeWorldRestoreState::default(),
|
world_restore: RuntimeWorldRestoreState::default(),
|
||||||
metadata: BTreeMap::new(),
|
metadata: BTreeMap::new(),
|
||||||
companies: Vec::new(),
|
companies: Vec::new(),
|
||||||
|
selected_company_id: None,
|
||||||
packed_event_collection: Some(RuntimePackedEventCollectionSummary {
|
packed_event_collection: Some(RuntimePackedEventCollectionSummary {
|
||||||
source_kind: "packed-event-runtime-collection".to_string(),
|
source_kind: "packed-event-runtime-collection".to_string(),
|
||||||
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
||||||
|
|
@ -307,6 +353,7 @@ mod tests {
|
||||||
standalone_condition_rows: Vec::new(),
|
standalone_condition_rows: Vec::new(),
|
||||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||||
grouped_effect_rows: Vec::new(),
|
grouped_effect_rows: Vec::new(),
|
||||||
|
grouped_company_targets: Vec::new(),
|
||||||
decoded_actions: Vec::new(),
|
decoded_actions: Vec::new(),
|
||||||
executable_import_ready: false,
|
executable_import_ready: false,
|
||||||
import_outcome: Some("blocked_missing_compact_control".to_string()),
|
import_outcome: Some("blocked_missing_compact_control".to_string()),
|
||||||
|
|
@ -329,6 +376,7 @@ mod tests {
|
||||||
standalone_condition_rows: Vec::new(),
|
standalone_condition_rows: Vec::new(),
|
||||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||||
grouped_effect_rows: Vec::new(),
|
grouped_effect_rows: Vec::new(),
|
||||||
|
grouped_company_targets: Vec::new(),
|
||||||
decoded_actions: Vec::new(),
|
decoded_actions: Vec::new(),
|
||||||
executable_import_ready: false,
|
executable_import_ready: false,
|
||||||
import_outcome: Some("blocked_missing_company_context".to_string()),
|
import_outcome: Some("blocked_missing_company_context".to_string()),
|
||||||
|
|
@ -347,5 +395,8 @@ mod tests {
|
||||||
assert_eq!(summary.packed_event_blocked_unmapped_real_descriptor_count, 0);
|
assert_eq!(summary.packed_event_blocked_unmapped_real_descriptor_count, 0);
|
||||||
assert_eq!(summary.packed_event_blocked_structural_only_count, 0);
|
assert_eq!(summary.packed_event_blocked_structural_only_count, 0);
|
||||||
assert_eq!(summary.packed_event_blocked_missing_company_context_count, 1);
|
assert_eq!(summary.packed_event_blocked_missing_company_context_count, 1);
|
||||||
|
assert_eq!(summary.packed_event_blocked_missing_selection_context_count, 0);
|
||||||
|
assert_eq!(summary.packed_event_blocked_missing_company_role_context_count, 0);
|
||||||
|
assert_eq!(summary.packed_event_blocked_missing_condition_context_count, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,12 +75,15 @@ The highest-value next passes are now:
|
||||||
|
|
||||||
- preserve the atlas and function map as the source of subsystem boundaries while continuing to
|
- preserve the atlas and function map as the source of subsystem boundaries while continuing to
|
||||||
avoid shell-first implementation bets
|
avoid shell-first implementation bets
|
||||||
- tighten the packed-event frontier from generic real-row structure into decoded real compact
|
- keep using overlay imports as the context bridge when selectively executable packed rows still
|
||||||
control, so parity rows carry mode, selector, one-shot, and grouped target-scope state directly
|
need live company state that save slices do not persist
|
||||||
- use overlay imports as the context bridge when selectively executable packed rows still need live
|
- treat normalized symbolic company targets as the active packed-event frontier now that
|
||||||
company state that save slices do not persist
|
`selected_company`, `human_companies`, and `ai_companies` import and execute through the runtime
|
||||||
- widen real packed-event executable coverage only after the compact-control and descriptor frontier
|
service path
|
||||||
is stable, not just after row framing is parsed
|
- widen real packed-event executable coverage only after the compact-control, symbolic target, and
|
||||||
|
descriptor frontier is stable, not just after row framing is parsed
|
||||||
|
- leave condition-relative company scopes explicit and blocked until condition evaluation has
|
||||||
|
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,
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,14 @@ Implemented today:
|
||||||
- checked-in runtime fixtures already cover deterministic stepping, periodic service, direct trigger
|
- checked-in runtime fixtures already cover deterministic stepping, periodic service, direct trigger
|
||||||
service, snapshot-backed inputs, save-slice-backed inputs, overlay-import-backed inputs,
|
service, snapshot-backed inputs, save-slice-backed inputs, overlay-import-backed inputs,
|
||||||
normalized state-fragment assertions, and imported packed-event execution
|
normalized state-fragment assertions, and imported packed-event execution
|
||||||
|
- overlay imports now preserve selected-company and controller-role context, and the normalized
|
||||||
|
company-target model can execute `selected_company`, `human_companies`, and `ai_companies`
|
||||||
|
symbolic scopes through the ordinary runtime service path while keeping condition-relative
|
||||||
|
company scopes explicitly blocked
|
||||||
|
|
||||||
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 `0x4e9a` compact-control decode and descriptor-frontier tightening, not another persistence
|
real grouped-descriptor semantic mapping on top of the now-stable compact-control and symbolic
|
||||||
scaffold pass.
|
target frontier, not another persistence scaffold pass.
|
||||||
|
|
||||||
## Why This Boundary
|
## Why This Boundary
|
||||||
|
|
||||||
|
|
@ -222,8 +226,10 @@ Current status:
|
||||||
decoded actions fit the current normalized runtime-effect model
|
decoded actions fit the current normalized runtime-effect model
|
||||||
- tracked save-slice documents now provide a repo-friendly captured-runtime path without checking in
|
- tracked save-slice documents now provide a repo-friendly captured-runtime path without checking in
|
||||||
raw `.smp` binaries
|
raw `.smp` binaries
|
||||||
- the remaining gap is wider packed target-family coverage plus company-import depth, not
|
- overlay-backed captured-runtime inputs now provide enough runtime company context for symbolic
|
||||||
first-pass captured-runtime plumbing
|
selected-company and controller-role scopes without inventing company state from save bytes alone
|
||||||
|
- the remaining gap is wider real grouped-descriptor semantic coverage plus condition evaluation,
|
||||||
|
not first-pass captured-runtime plumbing
|
||||||
|
|
||||||
### Milestone 4: Domain Expansion
|
### Milestone 4: Domain Expansion
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,9 @@
|
||||||
"packed_event_imported_runtime_record_count": 0,
|
"packed_event_imported_runtime_record_count": 0,
|
||||||
"packed_event_parity_only_record_count": 1,
|
"packed_event_parity_only_record_count": 1,
|
||||||
"packed_event_unsupported_record_count": 1,
|
"packed_event_unsupported_record_count": 1,
|
||||||
|
"packed_event_blocked_missing_condition_context_count": 1,
|
||||||
"packed_event_blocked_missing_compact_control_count": 0,
|
"packed_event_blocked_missing_compact_control_count": 0,
|
||||||
"packed_event_blocked_unmapped_real_descriptor_count": 1,
|
"packed_event_blocked_unmapped_real_descriptor_count": 0,
|
||||||
"packed_event_blocked_structural_only_count": 0,
|
"packed_event_blocked_structural_only_count": 0,
|
||||||
"event_runtime_record_count": 0,
|
"event_runtime_record_count": 0,
|
||||||
"total_company_cash": 0
|
"total_company_cash": 0
|
||||||
|
|
@ -51,11 +52,19 @@
|
||||||
"payload_family": "real_packed_v1",
|
"payload_family": "real_packed_v1",
|
||||||
"trigger_kind": 6,
|
"trigger_kind": 6,
|
||||||
"one_shot": true,
|
"one_shot": true,
|
||||||
"import_outcome": "blocked_unmapped_real_descriptor",
|
"import_outcome": "blocked_missing_condition_context",
|
||||||
"compact_control": {
|
"compact_control": {
|
||||||
"primary_selector_0x7f0": 99,
|
"primary_selector_0x7f0": 99,
|
||||||
"grouped_target_scope_ordinals_0x7fb": [0, 1, 2, 3]
|
"grouped_target_scope_ordinals_0x7fb": [0, 1, 2, 3]
|
||||||
},
|
},
|
||||||
|
"grouped_company_targets": [
|
||||||
|
{
|
||||||
|
"kind": "condition_true_company"
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
],
|
||||||
"standalone_condition_rows": [
|
"standalone_condition_rows": [
|
||||||
{
|
{
|
||||||
"candidate_name": "AutoPlant"
|
"candidate_name": "AutoPlant"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"snapshot_id": "packed-event-symbolic-company-scope-overlay-base-snapshot",
|
||||||
|
"source": {
|
||||||
|
"description": "Base runtime snapshot supplying selected-company and controller-role context for symbolic packed-event targets."
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"calendar": {
|
||||||
|
"year": 1840,
|
||||||
|
"month_slot": 0,
|
||||||
|
"phase_slot": 1,
|
||||||
|
"tick_slot": 2
|
||||||
|
},
|
||||||
|
"world_flags": {
|
||||||
|
"base.only": true
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"base.note": "symbolic target context"
|
||||||
|
},
|
||||||
|
"companies": [
|
||||||
|
{
|
||||||
|
"company_id": 1,
|
||||||
|
"controller_kind": "human",
|
||||||
|
"current_cash": 100,
|
||||||
|
"debt": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"company_id": 2,
|
||||||
|
"controller_kind": "ai",
|
||||||
|
"current_cash": 50,
|
||||||
|
"debt": 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"company_id": 3,
|
||||||
|
"controller_kind": "human",
|
||||||
|
"current_cash": 70,
|
||||||
|
"debt": 30
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"selected_company_id": 3,
|
||||||
|
"event_runtime_records": [],
|
||||||
|
"candidate_availability": {},
|
||||||
|
"special_conditions": {},
|
||||||
|
"service_state": {
|
||||||
|
"periodic_boundary_calls": 0,
|
||||||
|
"trigger_dispatch_counts": {},
|
||||||
|
"total_event_record_services": 0,
|
||||||
|
"dirty_rerun_count": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"fixture_id": "packed-event-symbolic-company-scope-overlay-fixture",
|
||||||
|
"source": {
|
||||||
|
"kind": "captured-runtime",
|
||||||
|
"description": "Fixture backed by an overlay import document so symbolic company-target packed events execute against selected-company and controller-role context."
|
||||||
|
},
|
||||||
|
"state_import_path": "packed-event-symbolic-company-scope-overlay.json",
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"kind": "service_trigger_kind",
|
||||||
|
"trigger_kind": 7
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"expected_summary": {
|
||||||
|
"calendar_projection_source": "base-snapshot-preserved",
|
||||||
|
"calendar_projection_is_placeholder": false,
|
||||||
|
"company_count": 3,
|
||||||
|
"packed_event_collection_present": true,
|
||||||
|
"packed_event_record_count": 4,
|
||||||
|
"packed_event_decoded_record_count": 4,
|
||||||
|
"packed_event_imported_runtime_record_count": 3,
|
||||||
|
"packed_event_parity_only_record_count": 1,
|
||||||
|
"packed_event_unsupported_record_count": 0,
|
||||||
|
"packed_event_blocked_missing_condition_context_count": 1,
|
||||||
|
"event_runtime_record_count": 3,
|
||||||
|
"total_event_record_service_count": 3,
|
||||||
|
"total_trigger_dispatch_count": 1,
|
||||||
|
"dirty_rerun_count": 0,
|
||||||
|
"total_company_cash": 244
|
||||||
|
},
|
||||||
|
"expected_state_fragment": {
|
||||||
|
"selected_company_id": 3,
|
||||||
|
"companies": [
|
||||||
|
{
|
||||||
|
"company_id": 1,
|
||||||
|
"controller_kind": "human",
|
||||||
|
"current_cash": 100,
|
||||||
|
"debt": 14
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"company_id": 2,
|
||||||
|
"controller_kind": "ai",
|
||||||
|
"current_cash": 59,
|
||||||
|
"debt": 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"company_id": 3,
|
||||||
|
"controller_kind": "human",
|
||||||
|
"current_cash": 85,
|
||||||
|
"debt": 34
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packed_event_collection": {
|
||||||
|
"live_entry_ids": [21, 22, 23, 24],
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"import_outcome": "imported"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"import_outcome": "imported"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"import_outcome": "imported"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"import_outcome": "blocked_missing_condition_context"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"event_runtime_records": [
|
||||||
|
{
|
||||||
|
"record_id": 21,
|
||||||
|
"service_count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"record_id": 22,
|
||||||
|
"service_count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"record_id": 23,
|
||||||
|
"service_count": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"import_id": "packed-event-symbolic-company-scope-overlay",
|
||||||
|
"source": {
|
||||||
|
"description": "Overlay import that combines a selected-company snapshot with symbolic company-target packed events.",
|
||||||
|
"notes": [
|
||||||
|
"used to prove selected, human, and ai symbolic company targets import through the normalized runtime path"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"base_snapshot_path": "packed-event-symbolic-company-scope-overlay-base-snapshot.json",
|
||||||
|
"save_slice_path": "packed-event-symbolic-company-scope-save-slice.json"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,165 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"save_slice_id": "packed-event-symbolic-company-scope-save-slice",
|
||||||
|
"source": {
|
||||||
|
"description": "Tracked save-slice document with synthetic packed-event records that use symbolic company targets.",
|
||||||
|
"original_save_filename": "captured-symbolic-company-scope.gms",
|
||||||
|
"original_save_sha256": "symbolic-company-scope-sample-sha256",
|
||||||
|
"notes": [
|
||||||
|
"tracked as JSON save-slice document rather than raw .smp",
|
||||||
|
"locks selected-company and controller-role import behavior"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"save_slice": {
|
||||||
|
"file_extension_hint": "gms",
|
||||||
|
"container_profile_family": "rt3-classic-save-container-v1",
|
||||||
|
"mechanism_family": "classic-save-rehydrate-v1",
|
||||||
|
"mechanism_confidence": "grounded",
|
||||||
|
"trailer_family": null,
|
||||||
|
"bridge_family": null,
|
||||||
|
"profile": null,
|
||||||
|
"candidate_availability_table": null,
|
||||||
|
"special_conditions_table": null,
|
||||||
|
"event_runtime_collection": {
|
||||||
|
"source_kind": "packed-event-runtime-collection",
|
||||||
|
"mechanism_family": "classic-save-rehydrate-v1",
|
||||||
|
"mechanism_confidence": "grounded",
|
||||||
|
"container_profile_family": "rt3-classic-save-container-v1",
|
||||||
|
"metadata_tag_offset": 28928,
|
||||||
|
"records_tag_offset": 29184,
|
||||||
|
"close_tag_offset": 29952,
|
||||||
|
"packed_state_version": 1001,
|
||||||
|
"packed_state_version_hex": "0x000003e9",
|
||||||
|
"live_id_bound": 24,
|
||||||
|
"live_record_count": 4,
|
||||||
|
"live_entry_ids": [21, 22, 23, 24],
|
||||||
|
"decoded_record_count": 4,
|
||||||
|
"imported_runtime_record_count": 3,
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"record_index": 0,
|
||||||
|
"live_entry_id": 21,
|
||||||
|
"payload_offset": 29186,
|
||||||
|
"payload_len": 56,
|
||||||
|
"decode_status": "executable",
|
||||||
|
"payload_family": "synthetic_harness",
|
||||||
|
"trigger_kind": 7,
|
||||||
|
"active": true,
|
||||||
|
"marks_collection_dirty": false,
|
||||||
|
"one_shot": false,
|
||||||
|
"text_bands": [],
|
||||||
|
"standalone_condition_row_count": 0,
|
||||||
|
"standalone_condition_rows": [],
|
||||||
|
"grouped_effect_row_counts": [0, 0, 0, 0],
|
||||||
|
"grouped_effect_rows": [],
|
||||||
|
"decoded_actions": [
|
||||||
|
{
|
||||||
|
"kind": "adjust_company_cash",
|
||||||
|
"target": {
|
||||||
|
"kind": "selected_company"
|
||||||
|
},
|
||||||
|
"delta": 15
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"executable_import_ready": true,
|
||||||
|
"notes": [
|
||||||
|
"selected-company symbolic target"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"record_index": 1,
|
||||||
|
"live_entry_id": 22,
|
||||||
|
"payload_offset": 29242,
|
||||||
|
"payload_len": 56,
|
||||||
|
"decode_status": "executable",
|
||||||
|
"payload_family": "synthetic_harness",
|
||||||
|
"trigger_kind": 7,
|
||||||
|
"active": true,
|
||||||
|
"marks_collection_dirty": false,
|
||||||
|
"one_shot": false,
|
||||||
|
"text_bands": [],
|
||||||
|
"standalone_condition_row_count": 0,
|
||||||
|
"standalone_condition_rows": [],
|
||||||
|
"grouped_effect_row_counts": [0, 0, 0, 0],
|
||||||
|
"grouped_effect_rows": [],
|
||||||
|
"decoded_actions": [
|
||||||
|
{
|
||||||
|
"kind": "adjust_company_debt",
|
||||||
|
"target": {
|
||||||
|
"kind": "human_companies"
|
||||||
|
},
|
||||||
|
"delta": 4
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"executable_import_ready": true,
|
||||||
|
"notes": [
|
||||||
|
"human-company symbolic target"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"record_index": 2,
|
||||||
|
"live_entry_id": 23,
|
||||||
|
"payload_offset": 29298,
|
||||||
|
"payload_len": 56,
|
||||||
|
"decode_status": "executable",
|
||||||
|
"payload_family": "synthetic_harness",
|
||||||
|
"trigger_kind": 7,
|
||||||
|
"active": true,
|
||||||
|
"marks_collection_dirty": false,
|
||||||
|
"one_shot": false,
|
||||||
|
"text_bands": [],
|
||||||
|
"standalone_condition_row_count": 0,
|
||||||
|
"standalone_condition_rows": [],
|
||||||
|
"grouped_effect_row_counts": [0, 0, 0, 0],
|
||||||
|
"grouped_effect_rows": [],
|
||||||
|
"decoded_actions": [
|
||||||
|
{
|
||||||
|
"kind": "adjust_company_cash",
|
||||||
|
"target": {
|
||||||
|
"kind": "ai_companies"
|
||||||
|
},
|
||||||
|
"delta": 9
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"executable_import_ready": true,
|
||||||
|
"notes": [
|
||||||
|
"ai-company symbolic target"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"record_index": 3,
|
||||||
|
"live_entry_id": 24,
|
||||||
|
"payload_offset": 29354,
|
||||||
|
"payload_len": 56,
|
||||||
|
"decode_status": "parity_only",
|
||||||
|
"payload_family": "synthetic_harness",
|
||||||
|
"trigger_kind": 7,
|
||||||
|
"active": true,
|
||||||
|
"marks_collection_dirty": false,
|
||||||
|
"one_shot": false,
|
||||||
|
"text_bands": [],
|
||||||
|
"standalone_condition_row_count": 0,
|
||||||
|
"standalone_condition_rows": [],
|
||||||
|
"grouped_effect_row_counts": [0, 0, 0, 0],
|
||||||
|
"grouped_effect_rows": [],
|
||||||
|
"decoded_actions": [
|
||||||
|
{
|
||||||
|
"kind": "adjust_company_debt",
|
||||||
|
"target": {
|
||||||
|
"kind": "condition_true_company"
|
||||||
|
},
|
||||||
|
"delta": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"executable_import_ready": false,
|
||||||
|
"notes": [
|
||||||
|
"condition-relative symbolic target remains blocked"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notes": [
|
||||||
|
"symbolic company target sample"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue