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
|
||||
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
|
||||
frontier is real `0x4e9a` compact-control decode and descriptor-frontier tightening on top of the
|
||||
existing save-slice, snapshot, and overlay-import workflows. The PE32 hook remains useful as
|
||||
capture and integration tooling, but it is no longer the main execution milestone.
|
||||
frontier is real grouped-descriptor semantic mapping on top of the existing save-slice, snapshot,
|
||||
overlay-import, compact-control, and symbolic company-target workflows. The runtime already carries
|
||||
selected-company and controller-role context through overlay imports 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
|
||||
|
||||
|
|
|
|||
|
|
@ -4440,6 +4440,8 @@ mod tests {
|
|||
.join("../../fixtures/runtime/packed-event-selective-import-save-slice-fixture.json");
|
||||
let overlay_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.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)
|
||||
.expect("save-slice-backed parity fixture should summarize");
|
||||
|
|
@ -4447,6 +4449,8 @@ mod tests {
|
|||
.expect("save-slice-backed selective-import fixture should summarize");
|
||||
run_runtime_summarize_fixture(&overlay_fixture)
|
||||
.expect("overlay-backed selective-import fixture should summarize");
|
||||
run_runtime_summarize_fixture(&symbolic_overlay_fixture)
|
||||
.expect("overlay-backed symbolic-target fixture should summarize");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -173,6 +173,7 @@ mod tests {
|
|||
world_restore: RuntimeWorldRestoreState::default(),
|
||||
metadata: BTreeMap::new(),
|
||||
companies: Vec::new(),
|
||||
selected_company_id: None,
|
||||
packed_event_collection: None,
|
||||
event_runtime_records: Vec::new(),
|
||||
candidate_availability: BTreeMap::new(),
|
||||
|
|
@ -326,9 +327,11 @@ mod tests {
|
|||
metadata: BTreeMap::new(),
|
||||
companies: vec![rrt_runtime::RuntimeCompany {
|
||||
company_id: 42,
|
||||
controller_kind: rrt_runtime::RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 100,
|
||||
debt: 0,
|
||||
}],
|
||||
selected_company_id: Some(42),
|
||||
packed_event_collection: None,
|
||||
event_runtime_records: Vec::new(),
|
||||
candidate_availability: BTreeMap::new(),
|
||||
|
|
|
|||
|
|
@ -76,6 +76,12 @@ pub struct ExpectedRuntimeSummary {
|
|||
#[serde(default)]
|
||||
pub packed_event_blocked_missing_company_context_count: Option<usize>,
|
||||
#[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>,
|
||||
#[serde(default)]
|
||||
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 actual.packed_event_blocked_missing_compact_control_count != count {
|
||||
mismatches.push(format!(
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use crate::persistence::{load_runtime_snapshot_document, validate_runtime_snapshot_document};
|
||||
use crate::{
|
||||
CalendarPoint, RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate,
|
||||
CalendarPoint, RuntimeCompanyControllerKind, RuntimeCompanyTarget, RuntimeEffect,
|
||||
RuntimeEventRecord, RuntimeEventRecordTemplate,
|
||||
RuntimePackedEventCompactControlSummary,
|
||||
RuntimePackedEventConditionRowSummary, RuntimePackedEventGroupedEffectRowSummary,
|
||||
RuntimePackedEventCollectionSummary, RuntimePackedEventRecordSummary,
|
||||
|
|
@ -99,6 +100,47 @@ enum SaveSliceProjectionMode {
|
|||
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(
|
||||
save_slice: &SmpLoadedSaveSlice,
|
||||
import_id: &str,
|
||||
|
|
@ -109,7 +151,7 @@ pub fn project_save_slice_to_runtime_state_import(
|
|||
}
|
||||
let projection = project_save_slice_components(
|
||||
save_slice,
|
||||
&BTreeSet::new(),
|
||||
&ImportCompanyContext::standalone(),
|
||||
SaveSliceProjectionMode::Standalone,
|
||||
)?;
|
||||
|
||||
|
|
@ -125,6 +167,7 @@ pub fn project_save_slice_to_runtime_state_import(
|
|||
world_restore: projection.world_restore,
|
||||
metadata: projection.metadata,
|
||||
companies: Vec::new(),
|
||||
selected_company_id: None,
|
||||
packed_event_collection: projection.packed_event_collection,
|
||||
event_runtime_records: projection.event_runtime_records,
|
||||
candidate_availability: projection.candidate_availability,
|
||||
|
|
@ -151,14 +194,10 @@ pub fn project_save_slice_overlay_to_runtime_state_import(
|
|||
}
|
||||
base_state.validate()?;
|
||||
|
||||
let known_company_ids = base_state
|
||||
.companies
|
||||
.iter()
|
||||
.map(|company| company.company_id)
|
||||
.collect::<BTreeSet<_>>();
|
||||
let company_context = ImportCompanyContext::from_runtime_state(base_state);
|
||||
let projection = project_save_slice_components(
|
||||
save_slice,
|
||||
&known_company_ids,
|
||||
&company_context,
|
||||
SaveSliceProjectionMode::Overlay,
|
||||
)?;
|
||||
|
||||
|
|
@ -177,6 +216,7 @@ pub fn project_save_slice_overlay_to_runtime_state_import(
|
|||
world_restore: projection.world_restore,
|
||||
metadata,
|
||||
companies: base_state.companies.clone(),
|
||||
selected_company_id: base_state.selected_company_id,
|
||||
packed_event_collection: projection.packed_event_collection,
|
||||
event_runtime_records: projection.event_runtime_records,
|
||||
candidate_availability: projection.candidate_availability,
|
||||
|
|
@ -194,7 +234,7 @@ pub fn project_save_slice_overlay_to_runtime_state_import(
|
|||
|
||||
fn project_save_slice_components(
|
||||
save_slice: &SmpLoadedSaveSlice,
|
||||
known_company_ids: &BTreeSet<u32>,
|
||||
company_context: &ImportCompanyContext,
|
||||
mode: SaveSliceProjectionMode,
|
||||
) -> Result<SaveSliceProjection, String> {
|
||||
let mut world_flags = BTreeMap::new();
|
||||
|
|
@ -301,7 +341,7 @@ fn project_save_slice_components(
|
|||
}
|
||||
|
||||
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 {
|
||||
metadata.insert(
|
||||
"save_slice.event_runtime_collection_source_kind".to_string(),
|
||||
|
|
@ -491,7 +531,7 @@ fn project_save_slice_components(
|
|||
|
||||
fn project_packed_event_collection(
|
||||
save_slice: &SmpLoadedSaveSlice,
|
||||
known_company_ids: &BTreeSet<u32>,
|
||||
company_context: &ImportCompanyContext,
|
||||
) -> Result<
|
||||
(
|
||||
Option<RuntimePackedEventCollectionSummary>,
|
||||
|
|
@ -506,9 +546,7 @@ fn project_packed_event_collection(
|
|||
let mut imported_runtime_records = Vec::new();
|
||||
let mut imported_record_ids = BTreeSet::new();
|
||||
for record in &summary.records {
|
||||
if let Some(import_result) =
|
||||
smp_packed_record_to_runtime_event_record(record, known_company_ids)
|
||||
{
|
||||
if let Some(import_result) = smp_packed_record_to_runtime_event_record(record, company_context) {
|
||||
let runtime_record = import_result?;
|
||||
imported_record_ids.insert(record.live_entry_id);
|
||||
imported_runtime_records.push(runtime_record);
|
||||
|
|
@ -521,7 +559,7 @@ fn project_packed_event_collection(
|
|||
.map(|record| {
|
||||
runtime_packed_event_record_summary_from_smp(
|
||||
record,
|
||||
known_company_ids,
|
||||
company_context,
|
||||
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(
|
||||
record: &SmpLoadedPackedEventRecordSummary,
|
||||
known_company_ids: &BTreeSet<u32>,
|
||||
company_context: &ImportCompanyContext,
|
||||
imported: bool,
|
||||
) -> RuntimePackedEventRecordSummary {
|
||||
RuntimePackedEventRecordSummary {
|
||||
|
|
@ -586,11 +624,12 @@ fn runtime_packed_event_record_summary_from_smp(
|
|||
.iter()
|
||||
.map(runtime_packed_event_grouped_effect_row_summary_from_smp)
|
||||
.collect(),
|
||||
grouped_company_targets: classify_real_grouped_company_targets(record),
|
||||
decoded_actions: record.decoded_actions.clone(),
|
||||
executable_import_ready: record.executable_import_ready,
|
||||
import_outcome: Some(determine_packed_event_import_outcome(
|
||||
record,
|
||||
known_company_ids,
|
||||
company_context,
|
||||
imported,
|
||||
)),
|
||||
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(
|
||||
record: &SmpLoadedPackedEventRecordSummary,
|
||||
known_company_ids: &BTreeSet<u32>,
|
||||
company_context: &ImportCompanyContext,
|
||||
) -> Option<Result<RuntimeEventRecord, String>> {
|
||||
if record.decode_status == "unsupported_framing" || record.payload_family == "real_packed_v1" {
|
||||
return None;
|
||||
}
|
||||
|
||||
let effects =
|
||||
match smp_runtime_effects_to_runtime_effects(&record.decoded_actions, known_company_ids) {
|
||||
Ok(effects) => effects,
|
||||
Err(err) if err.contains("unresolved company ids") => return None,
|
||||
Err(_) => return None,
|
||||
};
|
||||
let effects = match smp_runtime_effects_to_runtime_effects(&record.decoded_actions, company_context) {
|
||||
Ok(effects) => effects,
|
||||
Err(_) => return None,
|
||||
};
|
||||
|
||||
Some((|| {
|
||||
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(
|
||||
effects: &[RuntimeEffect],
|
||||
known_company_ids: &BTreeSet<u32>,
|
||||
company_context: &ImportCompanyContext,
|
||||
) -> Result<Vec<RuntimeEffect>, String> {
|
||||
effects
|
||||
.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()
|
||||
}
|
||||
|
||||
fn smp_runtime_effect_to_runtime_effect(
|
||||
effect: &RuntimeEffect,
|
||||
known_company_ids: &BTreeSet<u32>,
|
||||
company_context: &ImportCompanyContext,
|
||||
) -> Result<RuntimeEffect, String> {
|
||||
match effect {
|
||||
RuntimeEffect::SetWorldFlag { key, value } => Ok(RuntimeEffect::SetWorldFlag {
|
||||
|
|
@ -731,23 +768,23 @@ fn smp_runtime_effect_to_runtime_effect(
|
|||
value: *value,
|
||||
}),
|
||||
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 {
|
||||
target: target.clone(),
|
||||
delta: *delta,
|
||||
})
|
||||
} 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 } => {
|
||||
if company_target_supported_for_import(target, known_company_ids) {
|
||||
if company_target_import_blocker(target, company_context).is_none() {
|
||||
Ok(RuntimeEffect::AdjustCompanyDebt {
|
||||
target: target.clone(),
|
||||
delta: *delta,
|
||||
})
|
||||
} 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 } => {
|
||||
|
|
@ -765,7 +802,7 @@ fn smp_runtime_effect_to_runtime_effect(
|
|||
RuntimeEffect::AppendEventRecord { record } => Ok(RuntimeEffect::AppendEventRecord {
|
||||
record: Box::new(smp_runtime_record_template_to_runtime(
|
||||
record,
|
||||
known_company_ids,
|
||||
company_context,
|
||||
)?),
|
||||
}),
|
||||
RuntimeEffect::ActivateEventRecord { record_id } => {
|
||||
|
|
@ -786,7 +823,7 @@ fn smp_runtime_effect_to_runtime_effect(
|
|||
|
||||
fn smp_runtime_record_template_to_runtime(
|
||||
record: &RuntimeEventRecordTemplate,
|
||||
known_company_ids: &BTreeSet<u32>,
|
||||
company_context: &ImportCompanyContext,
|
||||
) -> Result<RuntimeEventRecordTemplate, String> {
|
||||
Ok(RuntimeEventRecordTemplate {
|
||||
record_id: record.record_id,
|
||||
|
|
@ -794,28 +831,71 @@ fn smp_runtime_record_template_to_runtime(
|
|||
active: record.active,
|
||||
marks_collection_dirty: record.marks_collection_dirty,
|
||||
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(
|
||||
target: &crate::RuntimeCompanyTarget,
|
||||
known_company_ids: &BTreeSet<u32>,
|
||||
) -> bool {
|
||||
fn company_target_import_blocker(
|
||||
target: &RuntimeCompanyTarget,
|
||||
company_context: &ImportCompanyContext,
|
||||
) -> Option<CompanyTargetImportBlocker> {
|
||||
match target {
|
||||
crate::RuntimeCompanyTarget::AllActive => true,
|
||||
crate::RuntimeCompanyTarget::Ids { ids } => {
|
||||
!ids.is_empty()
|
||||
&& ids
|
||||
RuntimeCompanyTarget::AllActive => None,
|
||||
RuntimeCompanyTarget::Ids { ids } => {
|
||||
if ids.is_empty()
|
||||
|| ids
|
||||
.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(
|
||||
record: &SmpLoadedPackedEventRecordSummary,
|
||||
known_company_ids: &BTreeSet<u32>,
|
||||
company_context: &ImportCompanyContext,
|
||||
imported: bool,
|
||||
) -> String {
|
||||
if imported {
|
||||
|
|
@ -828,42 +908,105 @@ fn determine_packed_event_import_outcome(
|
|||
if record.compact_control.is_none() {
|
||||
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();
|
||||
}
|
||||
if packed_record_requires_missing_company_context(record, known_company_ids) {
|
||||
return "blocked_missing_company_context".to_string();
|
||||
if let Some(blocker) = packed_record_company_target_import_blocker(record, company_context) {
|
||||
return company_target_import_outcome(blocker).to_string();
|
||||
}
|
||||
"blocked_unsupported_decode".to_string()
|
||||
}
|
||||
|
||||
fn packed_record_requires_missing_company_context(
|
||||
fn packed_record_company_target_import_blocker(
|
||||
record: &SmpLoadedPackedEventRecordSummary,
|
||||
known_company_ids: &BTreeSet<u32>,
|
||||
) -> bool {
|
||||
company_context: &ImportCompanyContext,
|
||||
) -> Option<CompanyTargetImportBlocker> {
|
||||
record
|
||||
.decoded_actions
|
||||
.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,
|
||||
known_company_ids: &BTreeSet<u32>,
|
||||
) -> bool {
|
||||
company_context: &ImportCompanyContext,
|
||||
) -> Option<CompanyTargetImportBlocker> {
|
||||
match effect {
|
||||
RuntimeEffect::AdjustCompanyCash { 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| {
|
||||
runtime_effect_requires_missing_company_context(nested, known_company_ids)
|
||||
}),
|
||||
RuntimeEffect::AppendEventRecord { record } => record
|
||||
.effects
|
||||
.iter()
|
||||
.find_map(|nested| runtime_effect_company_target_import_blocker(nested, company_context)),
|
||||
RuntimeEffect::SetWorldFlag { .. }
|
||||
| RuntimeEffect::SetCandidateAvailability { .. }
|
||||
| RuntimeEffect::SetSpecialCondition { .. }
|
||||
| RuntimeEffect::ActivateEventRecord { .. }
|
||||
| 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(),
|
||||
metadata: BTreeMap::new(),
|
||||
companies: Vec::new(),
|
||||
selected_company_id: None,
|
||||
packed_event_collection: None,
|
||||
event_runtime_records: Vec::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> {
|
||||
vec![crate::SmpLoadedPackedEventGroupedEffectRowSummary {
|
||||
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]
|
||||
fn loads_dump_document() {
|
||||
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]
|
||||
fn leaves_real_records_without_compact_control_blocked_missing_compact_control() {
|
||||
let save_slice = SmpLoadedSaveSlice {
|
||||
|
|
@ -1970,7 +2342,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[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 {
|
||||
file_extension_hint: Some("gms".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),
|
||||
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!(
|
||||
import
|
||||
.state
|
||||
|
|
@ -2063,9 +2505,11 @@ mod tests {
|
|||
metadata: BTreeMap::from([("base.note".to_string(), "kept".to_string())]),
|
||||
companies: vec![crate::RuntimeCompany {
|
||||
company_id: 42,
|
||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 500,
|
||||
debt: 20,
|
||||
}],
|
||||
selected_company_id: Some(42),
|
||||
packed_event_collection: None,
|
||||
event_runtime_records: vec![RuntimeEventRecord {
|
||||
record_id: 1,
|
||||
|
|
@ -2223,9 +2667,11 @@ mod tests {
|
|||
metadata: BTreeMap::new(),
|
||||
companies: vec![crate::RuntimeCompany {
|
||||
company_id: 42,
|
||||
controller_kind: RuntimeCompanyControllerKind::Human,
|
||||
current_cash: 100,
|
||||
debt: 0,
|
||||
}],
|
||||
selected_company_id: Some(42),
|
||||
packed_event_collection: None,
|
||||
event_runtime_records: Vec::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,
|
||||
};
|
||||
pub use runtime::{
|
||||
RuntimeCompany, RuntimeCompanyTarget, RuntimeEffect, RuntimeEventRecord,
|
||||
RuntimeCompany, RuntimeCompanyControllerKind, RuntimeCompanyTarget, RuntimeEffect, RuntimeEventRecord,
|
||||
RuntimeEventRecordTemplate, RuntimePackedEventCollectionSummary,
|
||||
RuntimePackedEventCompactControlSummary,
|
||||
RuntimePackedEventConditionRowSummary, RuntimePackedEventGroupedEffectRowSummary,
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ mod tests {
|
|||
world_restore: RuntimeWorldRestoreState::default(),
|
||||
metadata: BTreeMap::new(),
|
||||
companies: Vec::new(),
|
||||
selected_company_id: None,
|
||||
packed_event_collection: None,
|
||||
event_runtime_records: Vec::new(),
|
||||
candidate_availability: BTreeMap::new(),
|
||||
|
|
|
|||
|
|
@ -4,17 +4,32 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
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)]
|
||||
pub struct RuntimeCompany {
|
||||
pub company_id: u32,
|
||||
pub current_cash: i64,
|
||||
pub debt: u64,
|
||||
#[serde(default)]
|
||||
pub controller_kind: RuntimeCompanyControllerKind,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||
pub enum RuntimeCompanyTarget {
|
||||
AllActive,
|
||||
HumanCompanies,
|
||||
AiCompanies,
|
||||
SelectedCompany,
|
||||
ConditionTrueCompany,
|
||||
Ids { ids: Vec<u32> },
|
||||
}
|
||||
|
||||
|
|
@ -137,6 +152,8 @@ pub struct RuntimePackedEventRecordSummary {
|
|||
#[serde(default)]
|
||||
pub grouped_effect_rows: Vec<RuntimePackedEventGroupedEffectRowSummary>,
|
||||
#[serde(default)]
|
||||
pub grouped_company_targets: Vec<Option<RuntimeCompanyTarget>>,
|
||||
#[serde(default)]
|
||||
pub decoded_actions: Vec<RuntimeEffect>,
|
||||
#[serde(default)]
|
||||
pub executable_import_ready: bool,
|
||||
|
|
@ -303,6 +320,8 @@ pub struct RuntimeState {
|
|||
#[serde(default)]
|
||||
pub companies: Vec<RuntimeCompany>,
|
||||
#[serde(default)]
|
||||
pub selected_company_id: Option<u32>,
|
||||
#[serde(default)]
|
||||
pub packed_event_collection: Option<RuntimePackedEventCollectionSummary>,
|
||||
#[serde(default)]
|
||||
pub event_runtime_records: Vec<RuntimeEventRecord>,
|
||||
|
|
@ -324,6 +343,14 @@ impl RuntimeState {
|
|||
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();
|
||||
for record in &self.event_runtime_records {
|
||||
|
|
@ -672,7 +699,11 @@ fn validate_company_target(
|
|||
valid_company_ids: &BTreeSet<u32>,
|
||||
) -> Result<(), String> {
|
||||
match target {
|
||||
RuntimeCompanyTarget::AllActive => Ok(()),
|
||||
RuntimeCompanyTarget::AllActive
|
||||
| RuntimeCompanyTarget::HumanCompanies
|
||||
| RuntimeCompanyTarget::AiCompanies
|
||||
| RuntimeCompanyTarget::SelectedCompany
|
||||
| RuntimeCompanyTarget::ConditionTrueCompany => Ok(()),
|
||||
RuntimeCompanyTarget::Ids { ids } => {
|
||||
if ids.is_empty() {
|
||||
return Err("target ids must not be empty".to_string());
|
||||
|
|
@ -709,13 +740,16 @@ mod tests {
|
|||
company_id: 1,
|
||||
current_cash: 100,
|
||||
debt: 0,
|
||||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||
},
|
||||
RuntimeCompany {
|
||||
company_id: 1,
|
||||
current_cash: 200,
|
||||
debt: 0,
|
||||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||
},
|
||||
],
|
||||
selected_company_id: None,
|
||||
packed_event_collection: None,
|
||||
event_runtime_records: Vec::new(),
|
||||
candidate_availability: BTreeMap::new(),
|
||||
|
|
@ -762,6 +796,7 @@ mod tests {
|
|||
},
|
||||
metadata: BTreeMap::new(),
|
||||
companies: Vec::new(),
|
||||
selected_company_id: None,
|
||||
packed_event_collection: None,
|
||||
event_runtime_records: Vec::new(),
|
||||
candidate_availability: BTreeMap::new(),
|
||||
|
|
@ -789,7 +824,9 @@ mod tests {
|
|||
company_id: 1,
|
||||
current_cash: 100,
|
||||
debt: 0,
|
||||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||
}],
|
||||
selected_company_id: None,
|
||||
packed_event_collection: None,
|
||||
event_runtime_records: vec![RuntimeEventRecord {
|
||||
record_id: 7,
|
||||
|
|
@ -829,7 +866,9 @@ mod tests {
|
|||
company_id: 1,
|
||||
current_cash: 100,
|
||||
debt: 0,
|
||||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||
}],
|
||||
selected_company_id: None,
|
||||
packed_event_collection: None,
|
||||
event_runtime_records: vec![RuntimeEventRecord {
|
||||
record_id: 7,
|
||||
|
|
@ -875,6 +914,7 @@ mod tests {
|
|||
world_restore: RuntimeWorldRestoreState::default(),
|
||||
metadata: BTreeMap::new(),
|
||||
companies: Vec::new(),
|
||||
selected_company_id: None,
|
||||
packed_event_collection: Some(RuntimePackedEventCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
||||
|
|
@ -905,6 +945,7 @@ mod tests {
|
|||
standalone_condition_rows: Vec::new(),
|
||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||
grouped_effect_rows: Vec::new(),
|
||||
grouped_company_targets: Vec::new(),
|
||||
decoded_actions: Vec::new(),
|
||||
executable_import_ready: false,
|
||||
import_outcome: None,
|
||||
|
|
@ -927,6 +968,7 @@ mod tests {
|
|||
standalone_condition_rows: Vec::new(),
|
||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||
grouped_effect_rows: Vec::new(),
|
||||
grouped_company_targets: Vec::new(),
|
||||
decoded_actions: Vec::new(),
|
||||
executable_import_ready: false,
|
||||
import_outcome: None,
|
||||
|
|
@ -942,4 +984,34 @@ mod tests {
|
|||
|
||||
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 crate::{
|
||||
RuntimeCompanyTarget, RuntimeEffect, RuntimeEventRecordTemplate, RuntimeState, RuntimeSummary,
|
||||
RuntimeCompanyControllerKind, RuntimeCompanyTarget, RuntimeEffect, RuntimeEventRecordTemplate,
|
||||
RuntimeState, RuntimeSummary,
|
||||
calendar::BoundaryEventKind,
|
||||
};
|
||||
|
||||
|
|
@ -430,6 +431,49 @@ fn resolve_company_target_ids(
|
|||
}
|
||||
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 crate::{
|
||||
CalendarPoint, RuntimeCompany, RuntimeCompanyTarget, RuntimeEffect, RuntimeEventRecord,
|
||||
RuntimeEventRecordTemplate, RuntimeSaveProfileState, RuntimeServiceState,
|
||||
RuntimeWorldRestoreState,
|
||||
CalendarPoint, RuntimeCompany, RuntimeCompanyControllerKind, RuntimeCompanyTarget,
|
||||
RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate, RuntimeSaveProfileState,
|
||||
RuntimeServiceState, RuntimeWorldRestoreState,
|
||||
};
|
||||
|
||||
fn state() -> RuntimeState {
|
||||
|
|
@ -470,9 +514,11 @@ mod tests {
|
|||
metadata: BTreeMap::new(),
|
||||
companies: vec![RuntimeCompany {
|
||||
company_id: 1,
|
||||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||
current_cash: 10,
|
||||
debt: 0,
|
||||
}],
|
||||
selected_company_id: None,
|
||||
packed_event_collection: None,
|
||||
event_runtime_records: Vec::new(),
|
||||
candidate_availability: BTreeMap::new(),
|
||||
|
|
@ -630,11 +676,13 @@ mod tests {
|
|||
companies: vec![
|
||||
RuntimeCompany {
|
||||
company_id: 1,
|
||||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||
current_cash: 10,
|
||||
debt: 5,
|
||||
},
|
||||
RuntimeCompany {
|
||||
company_id: 2,
|
||||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||
current_cash: 20,
|
||||
debt: 8,
|
||||
},
|
||||
|
|
@ -674,6 +722,165 @@ mod tests {
|
|||
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]
|
||||
fn one_shot_record_only_fires_once() {
|
||||
let mut state = RuntimeState {
|
||||
|
|
@ -718,6 +925,7 @@ mod tests {
|
|||
let mut state = RuntimeState {
|
||||
companies: vec![RuntimeCompany {
|
||||
company_id: 1,
|
||||
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
||||
current_cash: 10,
|
||||
debt: 2,
|
||||
}],
|
||||
|
|
|
|||
|
|
@ -35,6 +35,9 @@ pub struct RuntimeSummary {
|
|||
pub packed_event_parity_only_record_count: usize,
|
||||
pub packed_event_unsupported_record_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_unmapped_real_descriptor_count: usize,
|
||||
pub packed_event_blocked_structural_only_count: usize,
|
||||
|
|
@ -171,6 +174,48 @@ impl RuntimeSummary {
|
|||
.count()
|
||||
})
|
||||
.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_collection
|
||||
.as_ref()
|
||||
|
|
@ -277,6 +322,7 @@ mod tests {
|
|||
world_restore: RuntimeWorldRestoreState::default(),
|
||||
metadata: BTreeMap::new(),
|
||||
companies: Vec::new(),
|
||||
selected_company_id: None,
|
||||
packed_event_collection: Some(RuntimePackedEventCollectionSummary {
|
||||
source_kind: "packed-event-runtime-collection".to_string(),
|
||||
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
||||
|
|
@ -307,6 +353,7 @@ mod tests {
|
|||
standalone_condition_rows: Vec::new(),
|
||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||
grouped_effect_rows: Vec::new(),
|
||||
grouped_company_targets: Vec::new(),
|
||||
decoded_actions: Vec::new(),
|
||||
executable_import_ready: false,
|
||||
import_outcome: Some("blocked_missing_compact_control".to_string()),
|
||||
|
|
@ -329,6 +376,7 @@ mod tests {
|
|||
standalone_condition_rows: Vec::new(),
|
||||
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
||||
grouped_effect_rows: Vec::new(),
|
||||
grouped_company_targets: Vec::new(),
|
||||
decoded_actions: Vec::new(),
|
||||
executable_import_ready: false,
|
||||
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_structural_only_count, 0);
|
||||
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
|
||||
avoid shell-first implementation bets
|
||||
- tighten the packed-event frontier from generic real-row structure into decoded real compact
|
||||
control, so parity rows carry mode, selector, one-shot, and grouped target-scope state directly
|
||||
- use overlay imports as the context bridge when selectively executable packed rows still need live
|
||||
company state that save slices do not persist
|
||||
- widen real packed-event executable coverage only after the compact-control and descriptor frontier
|
||||
is stable, not just after row framing is parsed
|
||||
- keep using overlay imports as the context bridge when selectively executable packed rows still
|
||||
need live company state that save slices do not persist
|
||||
- treat normalized symbolic company targets as the active packed-event frontier now that
|
||||
`selected_company`, `human_companies`, and `ai_companies` import and execute through the runtime
|
||||
service path
|
||||
- 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,
|
||||
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
|
||||
|
|
|
|||
|
|
@ -24,10 +24,14 @@ Implemented today:
|
|||
- checked-in runtime fixtures already cover deterministic stepping, periodic service, direct trigger
|
||||
service, snapshot-backed inputs, save-slice-backed inputs, overlay-import-backed inputs,
|
||||
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
|
||||
real `0x4e9a` compact-control decode and descriptor-frontier tightening, not another persistence
|
||||
scaffold pass.
|
||||
real grouped-descriptor semantic mapping on top of the now-stable compact-control and symbolic
|
||||
target frontier, not another persistence scaffold pass.
|
||||
|
||||
## Why This Boundary
|
||||
|
||||
|
|
@ -222,8 +226,10 @@ Current status:
|
|||
decoded actions fit the current normalized runtime-effect model
|
||||
- tracked save-slice documents now provide a repo-friendly captured-runtime path without checking in
|
||||
raw `.smp` binaries
|
||||
- the remaining gap is wider packed target-family coverage plus company-import depth, not
|
||||
first-pass captured-runtime plumbing
|
||||
- overlay-backed captured-runtime inputs now provide enough runtime company context for symbolic
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -26,8 +26,9 @@
|
|||
"packed_event_imported_runtime_record_count": 0,
|
||||
"packed_event_parity_only_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_unmapped_real_descriptor_count": 1,
|
||||
"packed_event_blocked_unmapped_real_descriptor_count": 0,
|
||||
"packed_event_blocked_structural_only_count": 0,
|
||||
"event_runtime_record_count": 0,
|
||||
"total_company_cash": 0
|
||||
|
|
@ -51,11 +52,19 @@
|
|||
"payload_family": "real_packed_v1",
|
||||
"trigger_kind": 6,
|
||||
"one_shot": true,
|
||||
"import_outcome": "blocked_unmapped_real_descriptor",
|
||||
"import_outcome": "blocked_missing_condition_context",
|
||||
"compact_control": {
|
||||
"primary_selector_0x7f0": 99,
|
||||
"grouped_target_scope_ordinals_0x7fb": [0, 1, 2, 3]
|
||||
},
|
||||
"grouped_company_targets": [
|
||||
{
|
||||
"kind": "condition_true_company"
|
||||
},
|
||||
null,
|
||||
null,
|
||||
null
|
||||
],
|
||||
"standalone_condition_rows": [
|
||||
{
|
||||
"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