Add save-slice-backed runtime fixtures
This commit is contained in:
parent
09b6514dbf
commit
8ca65cbbfb
12 changed files with 974 additions and 47 deletions
|
|
@ -16,13 +16,15 @@ use rrt_model::{
|
||||||
};
|
};
|
||||||
use rrt_runtime::{
|
use rrt_runtime::{
|
||||||
CAMPAIGN_SCENARIO_COUNT, CampaignExeInspectionReport, OBSERVED_CAMPAIGN_SCENARIO_NAMES,
|
CAMPAIGN_SCENARIO_COUNT, CampaignExeInspectionReport, OBSERVED_CAMPAIGN_SCENARIO_NAMES,
|
||||||
Pk4ExtractionReport, Pk4InspectionReport, RuntimeSnapshotDocument, RuntimeSnapshotSource,
|
Pk4ExtractionReport, Pk4InspectionReport, RuntimeSaveSliceDocument,
|
||||||
RuntimeSummary, SNAPSHOT_FORMAT_VERSION, SmpClassicPackedProfileBlock, SmpInspectionReport,
|
RuntimeSaveSliceDocumentSource, RuntimeSnapshotDocument, RuntimeSnapshotSource, RuntimeSummary,
|
||||||
SmpLoadedSaveSlice, SmpRt3105PackedProfileBlock, SmpSaveLoadSummary, WinInspectionReport,
|
SAVE_SLICE_DOCUMENT_FORMAT_VERSION, SNAPSHOT_FORMAT_VERSION, SmpClassicPackedProfileBlock,
|
||||||
execute_step_command, extract_pk4_entry_file, inspect_campaign_exe_file, inspect_pk4_file,
|
SmpInspectionReport, SmpLoadedSaveSlice, SmpRt3105PackedProfileBlock, SmpSaveLoadSummary,
|
||||||
inspect_smp_file, inspect_win_file, load_runtime_snapshot_document, load_runtime_state_import,
|
WinInspectionReport, execute_step_command, extract_pk4_entry_file, inspect_campaign_exe_file,
|
||||||
load_save_slice_file, project_save_slice_to_runtime_state_import,
|
inspect_pk4_file, inspect_smp_file, inspect_win_file, load_runtime_snapshot_document,
|
||||||
save_runtime_snapshot_document, validate_runtime_snapshot_document,
|
load_runtime_state_import, load_save_slice_file, project_save_slice_to_runtime_state_import,
|
||||||
|
save_runtime_save_slice_document, save_runtime_snapshot_document,
|
||||||
|
validate_runtime_snapshot_document,
|
||||||
};
|
};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
@ -122,6 +124,10 @@ enum Command {
|
||||||
smp_path: PathBuf,
|
smp_path: PathBuf,
|
||||||
output_path: PathBuf,
|
output_path: PathBuf,
|
||||||
},
|
},
|
||||||
|
RuntimeExportSaveSlice {
|
||||||
|
smp_path: PathBuf,
|
||||||
|
output_path: PathBuf,
|
||||||
|
},
|
||||||
RuntimeInspectPk4 {
|
RuntimeInspectPk4 {
|
||||||
pk4_path: PathBuf,
|
pk4_path: PathBuf,
|
||||||
},
|
},
|
||||||
|
|
@ -237,6 +243,13 @@ struct RuntimeLoadedSaveSliceOutput {
|
||||||
save_slice: SmpLoadedSaveSlice,
|
save_slice: SmpLoadedSaveSlice,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct RuntimeSaveSliceExportOutput {
|
||||||
|
path: String,
|
||||||
|
output_path: String,
|
||||||
|
save_slice_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
struct RuntimePk4InspectionOutput {
|
struct RuntimePk4InspectionOutput {
|
||||||
path: String,
|
path: String,
|
||||||
|
|
@ -770,6 +783,12 @@ fn real_main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
} => {
|
} => {
|
||||||
run_runtime_import_save_state(&smp_path, &output_path)?;
|
run_runtime_import_save_state(&smp_path, &output_path)?;
|
||||||
}
|
}
|
||||||
|
Command::RuntimeExportSaveSlice {
|
||||||
|
smp_path,
|
||||||
|
output_path,
|
||||||
|
} => {
|
||||||
|
run_runtime_export_save_slice(&smp_path, &output_path)?;
|
||||||
|
}
|
||||||
Command::RuntimeInspectPk4 { pk4_path } => {
|
Command::RuntimeInspectPk4 { pk4_path } => {
|
||||||
run_runtime_inspect_pk4(&pk4_path)?;
|
run_runtime_inspect_pk4(&pk4_path)?;
|
||||||
}
|
}
|
||||||
|
|
@ -929,6 +948,14 @@ fn parse_command() -> Result<Command, Box<dyn std::error::Error>> {
|
||||||
output_path: PathBuf::from(output_path),
|
output_path: PathBuf::from(output_path),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
[command, subcommand, smp_path, output_path]
|
||||||
|
if command == "runtime" && subcommand == "export-save-slice" =>
|
||||||
|
{
|
||||||
|
Ok(Command::RuntimeExportSaveSlice {
|
||||||
|
smp_path: PathBuf::from(smp_path),
|
||||||
|
output_path: PathBuf::from(output_path),
|
||||||
|
})
|
||||||
|
}
|
||||||
[command, subcommand, path] if command == "runtime" && subcommand == "inspect-pk4" => {
|
[command, subcommand, path] if command == "runtime" && subcommand == "inspect-pk4" => {
|
||||||
Ok(Command::RuntimeInspectPk4 {
|
Ok(Command::RuntimeInspectPk4 {
|
||||||
pk4_path: PathBuf::from(path),
|
pk4_path: PathBuf::from(path),
|
||||||
|
|
@ -1069,7 +1096,7 @@ fn parse_command() -> Result<Command, Box<dyn std::error::Error>> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_ => Err(
|
_ => Err(
|
||||||
"usage: rrt-cli [validate [repo-root] | finance eval <snapshot.json> | finance diff <left.json> <right.json> | runtime validate-fixture <fixture.json> | runtime summarize-fixture <fixture.json> | runtime export-fixture-state <fixture.json> <snapshot.json> | runtime diff-state <left.json> <right.json> | runtime summarize-state <snapshot.json> | runtime import-state <input.json> <snapshot.json> | runtime inspect-smp <file.smp> | runtime summarize-save-load <file.smp> | runtime load-save-slice <file.smp> | runtime import-save-state <file.smp> <snapshot.json> | runtime inspect-pk4 <file.pk4> | runtime inspect-win <file.win> | runtime extract-pk4-entry <file.pk4> <entry-name> <output-path> | runtime inspect-campaign-exe <RT3.exe> | runtime compare-classic-profile <save1.gms> <save2.gms> [saveN.gms...] | runtime compare-105-profile <save1.gms> <save2.gms> [saveN.gms...] | runtime compare-candidate-table <file1> <file2> [fileN...] | runtime compare-recipe-book-lines <file1> <file2> [fileN...] | runtime compare-setup-payload-core <file1> <file2> [fileN...] | runtime compare-setup-launch-payload <file1> <file2> [fileN...] | runtime compare-post-special-conditions-scalars <file1> <file2> [fileN...] | runtime scan-candidate-table-headers <root-dir> | runtime scan-special-conditions <root-dir> | runtime scan-aligned-runtime-rule-band <root-dir> | runtime scan-post-special-conditions-scalars <root-dir> | runtime scan-post-special-conditions-tail <root-dir> | runtime scan-recipe-book-lines <root-dir> | runtime export-profile-block <save.gms> <profile.json>]"
|
"usage: rrt-cli [validate [repo-root] | finance eval <snapshot.json> | finance diff <left.json> <right.json> | runtime validate-fixture <fixture.json> | runtime summarize-fixture <fixture.json> | runtime export-fixture-state <fixture.json> <snapshot.json> | runtime diff-state <left.json> <right.json> | runtime summarize-state <snapshot.json> | runtime import-state <input.json> <snapshot.json> | runtime inspect-smp <file.smp> | runtime summarize-save-load <file.smp> | runtime load-save-slice <file.smp> | runtime import-save-state <file.smp> <snapshot.json> | runtime export-save-slice <file.smp> <save-slice.json> | runtime inspect-pk4 <file.pk4> | runtime inspect-win <file.win> | runtime extract-pk4-entry <file.pk4> <entry-name> <output-path> | runtime inspect-campaign-exe <RT3.exe> | runtime compare-classic-profile <save1.gms> <save2.gms> [saveN.gms...] | runtime compare-105-profile <save1.gms> <save2.gms> [saveN.gms...] | runtime compare-candidate-table <file1> <file2> [fileN...] | runtime compare-recipe-book-lines <file1> <file2> [fileN...] | runtime compare-setup-payload-core <file1> <file2> [fileN...] | runtime compare-setup-launch-payload <file1> <file2> [fileN...] | runtime compare-post-special-conditions-scalars <file1> <file2> [fileN...] | runtime scan-candidate-table-headers <root-dir> | runtime scan-special-conditions <root-dir> | runtime scan-aligned-runtime-rule-band <root-dir> | runtime scan-post-special-conditions-scalars <root-dir> | runtime scan-post-special-conditions-tail <root-dir> | runtime scan-recipe-book-lines <root-dir> | runtime export-profile-block <save.gms> <profile.json>]"
|
||||||
.into(),
|
.into(),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
@ -1326,6 +1353,50 @@ fn run_runtime_import_save_state(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_runtime_export_save_slice(
|
||||||
|
smp_path: &Path,
|
||||||
|
output_path: &Path,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let save_slice = load_save_slice_file(smp_path)?;
|
||||||
|
let report = export_runtime_save_slice_document(smp_path, output_path, save_slice)?;
|
||||||
|
println!("{}", serde_json::to_string_pretty(&report)?);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn export_runtime_save_slice_document(
|
||||||
|
smp_path: &Path,
|
||||||
|
output_path: &Path,
|
||||||
|
save_slice: SmpLoadedSaveSlice,
|
||||||
|
) -> Result<RuntimeSaveSliceExportOutput, Box<dyn std::error::Error>> {
|
||||||
|
let document = RuntimeSaveSliceDocument {
|
||||||
|
format_version: SAVE_SLICE_DOCUMENT_FORMAT_VERSION,
|
||||||
|
save_slice_id: smp_path
|
||||||
|
.file_stem()
|
||||||
|
.and_then(|stem| stem.to_str())
|
||||||
|
.unwrap_or("save-slice")
|
||||||
|
.to_string(),
|
||||||
|
source: RuntimeSaveSliceDocumentSource {
|
||||||
|
description: Some(format!(
|
||||||
|
"Exported loaded save slice from {}",
|
||||||
|
smp_path.display()
|
||||||
|
)),
|
||||||
|
original_save_filename: smp_path
|
||||||
|
.file_name()
|
||||||
|
.and_then(|name| name.to_str())
|
||||||
|
.map(ToString::to_string),
|
||||||
|
original_save_sha256: None,
|
||||||
|
notes: vec![],
|
||||||
|
},
|
||||||
|
save_slice,
|
||||||
|
};
|
||||||
|
save_runtime_save_slice_document(output_path, &document)?;
|
||||||
|
Ok(RuntimeSaveSliceExportOutput {
|
||||||
|
path: smp_path.display().to_string(),
|
||||||
|
output_path: output_path.display().to_string(),
|
||||||
|
save_slice_id: document.save_slice_id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn run_runtime_inspect_pk4(pk4_path: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
fn run_runtime_inspect_pk4(pk4_path: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let report = RuntimePk4InspectionOutput {
|
let report = RuntimePk4InspectionOutput {
|
||||||
path: pk4_path.display().to_string(),
|
path: pk4_path.display().to_string(),
|
||||||
|
|
@ -4272,6 +4343,59 @@ mod tests {
|
||||||
.expect("snapshot-backed imported packed-event fixture should summarize");
|
.expect("snapshot-backed imported packed-event fixture should summarize");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn summarizes_save_slice_backed_fixtures() {
|
||||||
|
let parity_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||||
|
.join("../../fixtures/runtime/packed-event-parity-save-slice-fixture.json");
|
||||||
|
let selective_fixture = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||||
|
.join("../../fixtures/runtime/packed-event-selective-import-save-slice-fixture.json");
|
||||||
|
|
||||||
|
run_runtime_summarize_fixture(&parity_fixture)
|
||||||
|
.expect("save-slice-backed parity fixture should summarize");
|
||||||
|
run_runtime_summarize_fixture(&selective_fixture)
|
||||||
|
.expect("save-slice-backed selective-import fixture should summarize");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exports_runtime_save_slice_document_from_loaded_slice() {
|
||||||
|
let nonce = std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.expect("system time should be after epoch")
|
||||||
|
.as_nanos();
|
||||||
|
let output_path =
|
||||||
|
std::env::temp_dir().join(format!("rrt-export-save-slice-test-{nonce}.json"));
|
||||||
|
let smp_path = PathBuf::from("captured-test.gms");
|
||||||
|
|
||||||
|
let report = export_runtime_save_slice_document(
|
||||||
|
&smp_path,
|
||||||
|
&output_path,
|
||||||
|
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: None,
|
||||||
|
notes: vec!["exported for test".to_string()],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("save slice export should succeed");
|
||||||
|
|
||||||
|
assert_eq!(report.save_slice_id, "captured-test");
|
||||||
|
let document = rrt_runtime::load_runtime_save_slice_document(&output_path)
|
||||||
|
.expect("exported save slice document should load");
|
||||||
|
assert_eq!(document.save_slice_id, "captured-test");
|
||||||
|
assert_eq!(
|
||||||
|
document.source.original_save_filename.as_deref(),
|
||||||
|
Some("captured-test.gms")
|
||||||
|
);
|
||||||
|
let _ = fs::remove_file(output_path);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn diffs_runtime_states_with_packed_record_and_runtime_record_import_changes() {
|
fn diffs_runtime_states_with_packed_record_and_runtime_record_import_changes() {
|
||||||
let left = serde_json::json!({
|
let left = serde_json::json!({
|
||||||
|
|
@ -4412,6 +4536,25 @@ mod tests {
|
||||||
let _ = fs::remove_file(right_path);
|
let _ = fs::remove_file(right_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn diffs_save_slice_backed_states_across_packed_event_boundaries() {
|
||||||
|
let left_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||||
|
.join("../../fixtures/runtime/packed-event-parity-save-slice.json");
|
||||||
|
let right_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||||
|
.join("../../fixtures/runtime/packed-event-selective-import-save-slice.json");
|
||||||
|
|
||||||
|
let left_state = load_normalized_runtime_state(&left_path)
|
||||||
|
.expect("left save-slice-backed state should load");
|
||||||
|
let right_state = load_normalized_runtime_state(&right_path)
|
||||||
|
.expect("right save-slice-backed state should load");
|
||||||
|
let differences = diff_json_values(&left_state, &right_state);
|
||||||
|
|
||||||
|
assert!(differences.iter().any(|entry| {
|
||||||
|
entry.path == "$.packed_event_collection.imported_runtime_record_count"
|
||||||
|
|| entry.path == "$.packed_event_collection.records[0].decode_status"
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn diffs_classic_profile_samples_across_multiple_files() {
|
fn diffs_classic_profile_samples_across_multiple_files() {
|
||||||
let sample_a = RuntimeClassicProfileSample {
|
let sample_a = RuntimeClassicProfileSample {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use rrt_runtime::{load_runtime_snapshot_document, validate_runtime_snapshot_document};
|
use rrt_runtime::{
|
||||||
|
load_runtime_save_slice_document, load_runtime_snapshot_document,
|
||||||
|
project_save_slice_to_runtime_state_import, validate_runtime_save_slice_document,
|
||||||
|
validate_runtime_snapshot_document,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{FixtureDocument, FixtureStateOrigin, RawFixtureDocument};
|
use crate::{FixtureDocument, FixtureStateOrigin, RawFixtureDocument};
|
||||||
|
|
||||||
|
|
@ -28,17 +32,23 @@ fn resolve_raw_fixture_document(
|
||||||
raw: RawFixtureDocument,
|
raw: RawFixtureDocument,
|
||||||
base_dir: &Path,
|
base_dir: &Path,
|
||||||
) -> Result<FixtureDocument, Box<dyn std::error::Error>> {
|
) -> Result<FixtureDocument, Box<dyn std::error::Error>> {
|
||||||
let state = match (&raw.state, &raw.state_snapshot_path) {
|
let specified_state_inputs = usize::from(raw.state.is_some())
|
||||||
(Some(_), Some(_)) => {
|
+ usize::from(raw.state_snapshot_path.is_some())
|
||||||
return Err(
|
+ usize::from(raw.state_save_slice_path.is_some());
|
||||||
"fixture must not specify both inline state and state_snapshot_path".into(),
|
if specified_state_inputs != 1 {
|
||||||
);
|
return Err(
|
||||||
}
|
"fixture must specify exactly one of inline state, state_snapshot_path, or state_save_slice_path"
|
||||||
(None, None) => {
|
.into(),
|
||||||
return Err("fixture must specify either inline state or state_snapshot_path".into());
|
);
|
||||||
}
|
}
|
||||||
(Some(state), None) => state.clone(),
|
|
||||||
(None, Some(snapshot_path)) => {
|
let state = match (
|
||||||
|
&raw.state,
|
||||||
|
&raw.state_snapshot_path,
|
||||||
|
&raw.state_save_slice_path,
|
||||||
|
) {
|
||||||
|
(Some(state), None, None) => state.clone(),
|
||||||
|
(None, Some(snapshot_path), None) => {
|
||||||
let snapshot_path = resolve_snapshot_path(base_dir, snapshot_path);
|
let snapshot_path = resolve_snapshot_path(base_dir, snapshot_path);
|
||||||
let snapshot = load_runtime_snapshot_document(&snapshot_path)?;
|
let snapshot = load_runtime_snapshot_document(&snapshot_path)?;
|
||||||
validate_runtime_snapshot_document(&snapshot).map_err(|err| {
|
validate_runtime_snapshot_document(&snapshot).map_err(|err| {
|
||||||
|
|
@ -49,11 +59,35 @@ fn resolve_raw_fixture_document(
|
||||||
})?;
|
})?;
|
||||||
snapshot.state
|
snapshot.state
|
||||||
}
|
}
|
||||||
|
(None, None, Some(save_slice_path)) => {
|
||||||
|
let save_slice_path = resolve_snapshot_path(base_dir, save_slice_path);
|
||||||
|
let document = load_runtime_save_slice_document(&save_slice_path)?;
|
||||||
|
validate_runtime_save_slice_document(&document).map_err(|err| {
|
||||||
|
format!(
|
||||||
|
"invalid runtime save slice document {}: {err}",
|
||||||
|
save_slice_path.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
project_save_slice_to_runtime_state_import(
|
||||||
|
&document.save_slice,
|
||||||
|
&document.save_slice_id,
|
||||||
|
document.source.description.clone(),
|
||||||
|
)
|
||||||
|
.map_err(|err| {
|
||||||
|
format!(
|
||||||
|
"failed to project runtime save slice document {}: {err}",
|
||||||
|
save_slice_path.display()
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.state
|
||||||
|
}
|
||||||
|
_ => unreachable!("state input exclusivity checked above"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let state_origin = match raw.state_snapshot_path {
|
let state_origin = match (raw.state_snapshot_path, raw.state_save_slice_path) {
|
||||||
Some(snapshot_path) => FixtureStateOrigin::SnapshotPath(snapshot_path),
|
(Some(snapshot_path), None) => FixtureStateOrigin::SnapshotPath(snapshot_path),
|
||||||
None => FixtureStateOrigin::Inline,
|
(None, Some(save_slice_path)) => FixtureStateOrigin::SaveSlicePath(save_slice_path),
|
||||||
|
_ => FixtureStateOrigin::Inline,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(FixtureDocument {
|
Ok(FixtureDocument {
|
||||||
|
|
@ -82,9 +116,11 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::FixtureStateOrigin;
|
use crate::FixtureStateOrigin;
|
||||||
use rrt_runtime::{
|
use rrt_runtime::{
|
||||||
CalendarPoint, RuntimeSaveProfileState, RuntimeServiceState, RuntimeSnapshotDocument,
|
CalendarPoint, RuntimeSaveProfileState, RuntimeSaveSliceDocument,
|
||||||
RuntimeSnapshotSource, RuntimeState, RuntimeWorldRestoreState, SNAPSHOT_FORMAT_VERSION,
|
RuntimeSaveSliceDocumentSource, RuntimeServiceState, RuntimeSnapshotDocument,
|
||||||
save_runtime_snapshot_document,
|
RuntimeSnapshotSource, RuntimeState, RuntimeWorldRestoreState,
|
||||||
|
SAVE_SLICE_DOCUMENT_FORMAT_VERSION, SNAPSHOT_FORMAT_VERSION,
|
||||||
|
save_runtime_save_slice_document, save_runtime_snapshot_document,
|
||||||
};
|
};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
|
@ -167,4 +203,76 @@ mod tests {
|
||||||
let _ = std::fs::remove_file(snapshot_path);
|
let _ = std::fs::remove_file(snapshot_path);
|
||||||
let _ = std::fs::remove_dir(fixture_dir);
|
let _ = std::fs::remove_dir(fixture_dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn loads_fixture_from_relative_save_slice_path() {
|
||||||
|
let nonce = std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.expect("system time should be after epoch")
|
||||||
|
.as_nanos();
|
||||||
|
let fixture_dir = std::env::temp_dir().join(format!("rrt-fixture-save-slice-{nonce}"));
|
||||||
|
std::fs::create_dir_all(&fixture_dir).expect("fixture dir should be created");
|
||||||
|
|
||||||
|
let save_slice_path = fixture_dir.join("state-save-slice.json");
|
||||||
|
let save_slice = RuntimeSaveSliceDocument {
|
||||||
|
format_version: SAVE_SLICE_DOCUMENT_FORMAT_VERSION,
|
||||||
|
save_slice_id: "save-slice-backed-fixture-state".to_string(),
|
||||||
|
source: RuntimeSaveSliceDocumentSource {
|
||||||
|
description: Some("test save slice".to_string()),
|
||||||
|
original_save_filename: Some("fixture.gms".to_string()),
|
||||||
|
original_save_sha256: None,
|
||||||
|
notes: vec![],
|
||||||
|
},
|
||||||
|
save_slice: rrt_runtime::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: None,
|
||||||
|
notes: vec![],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
save_runtime_save_slice_document(&save_slice_path, &save_slice)
|
||||||
|
.expect("save slice should save");
|
||||||
|
|
||||||
|
let fixture_json = r#"
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"fixture_id": "save-slice-backed-fixture",
|
||||||
|
"source": {
|
||||||
|
"kind": "captured-runtime"
|
||||||
|
},
|
||||||
|
"state_save_slice_path": "state-save-slice.json",
|
||||||
|
"commands": [],
|
||||||
|
"expected_summary": {
|
||||||
|
"calendar_projection_is_placeholder": true,
|
||||||
|
"packed_event_collection_present": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let fixture = load_fixture_document_from_str_with_base(fixture_json, &fixture_dir)
|
||||||
|
.expect("save-slice-backed fixture should load");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
fixture.state_origin,
|
||||||
|
FixtureStateOrigin::SaveSlicePath("state-save-slice.json".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
fixture
|
||||||
|
.state
|
||||||
|
.metadata
|
||||||
|
.get("save_slice.import_projection")
|
||||||
|
.map(String::as_str),
|
||||||
|
Some("partial-runtime-restore-v1")
|
||||||
|
);
|
||||||
|
|
||||||
|
let _ = std::fs::remove_file(save_slice_path);
|
||||||
|
let _ = std::fs::remove_dir(fixture_dir);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,10 @@ pub struct ExpectedRuntimeSummary {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub packed_event_imported_runtime_record_count: Option<usize>,
|
pub packed_event_imported_runtime_record_count: Option<usize>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub packed_event_parity_only_record_count: Option<usize>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub packed_event_unsupported_record_count: Option<usize>,
|
||||||
|
#[serde(default)]
|
||||||
pub event_runtime_record_count: Option<usize>,
|
pub event_runtime_record_count: Option<usize>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub candidate_availability_count: Option<usize>,
|
pub candidate_availability_count: Option<usize>,
|
||||||
|
|
@ -341,6 +345,22 @@ impl ExpectedRuntimeSummary {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(count) = self.packed_event_parity_only_record_count {
|
||||||
|
if actual.packed_event_parity_only_record_count != count {
|
||||||
|
mismatches.push(format!(
|
||||||
|
"packed_event_parity_only_record_count mismatch: expected {count}, got {}",
|
||||||
|
actual.packed_event_parity_only_record_count
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(count) = self.packed_event_unsupported_record_count {
|
||||||
|
if actual.packed_event_unsupported_record_count != count {
|
||||||
|
mismatches.push(format!(
|
||||||
|
"packed_event_unsupported_record_count mismatch: expected {count}, got {}",
|
||||||
|
actual.packed_event_unsupported_record_count
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Some(count) = self.event_runtime_record_count {
|
if let Some(count) = self.event_runtime_record_count {
|
||||||
if actual.event_runtime_record_count != count {
|
if actual.event_runtime_record_count != count {
|
||||||
mismatches.push(format!(
|
mismatches.push(format!(
|
||||||
|
|
@ -510,6 +530,7 @@ pub struct FixtureDocument {
|
||||||
pub enum FixtureStateOrigin {
|
pub enum FixtureStateOrigin {
|
||||||
Inline,
|
Inline,
|
||||||
SnapshotPath(String),
|
SnapshotPath(String),
|
||||||
|
SaveSlicePath(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
|
@ -523,6 +544,8 @@ pub struct RawFixtureDocument {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub state_snapshot_path: Option<String>,
|
pub state_snapshot_path: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub state_save_slice_path: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
pub commands: Vec<StepCommand>,
|
pub commands: Vec<StepCommand>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub expected_summary: ExpectedRuntimeSummary,
|
pub expected_summary: ExpectedRuntimeSummary,
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const STATE_DUMP_FORMAT_VERSION: u32 = 1;
|
pub const STATE_DUMP_FORMAT_VERSION: u32 = 1;
|
||||||
|
pub const SAVE_SLICE_DOCUMENT_FORMAT_VERSION: u32 = 1;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||||
pub struct RuntimeStateDumpSource {
|
pub struct RuntimeStateDumpSource {
|
||||||
|
|
@ -30,6 +31,27 @@ pub struct RuntimeStateDumpDocument {
|
||||||
pub state: RuntimeState,
|
pub state: RuntimeState,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||||
|
pub struct RuntimeSaveSliceDocumentSource {
|
||||||
|
#[serde(default)]
|
||||||
|
pub description: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub original_save_filename: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub original_save_sha256: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub notes: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct RuntimeSaveSliceDocument {
|
||||||
|
pub format_version: u32,
|
||||||
|
pub save_slice_id: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub source: RuntimeSaveSliceDocumentSource,
|
||||||
|
pub save_slice: SmpLoadedSaveSlice,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct RuntimeStateImport {
|
pub struct RuntimeStateImport {
|
||||||
pub import_id: String,
|
pub import_id: String,
|
||||||
|
|
@ -581,6 +603,80 @@ pub fn validate_runtime_state_dump_document(
|
||||||
document.state.validate()
|
document.state.validate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn validate_runtime_save_slice_document(
|
||||||
|
document: &RuntimeSaveSliceDocument,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
if document.format_version != SAVE_SLICE_DOCUMENT_FORMAT_VERSION {
|
||||||
|
return Err(format!(
|
||||||
|
"unsupported save slice document format_version {} (expected {})",
|
||||||
|
document.format_version, SAVE_SLICE_DOCUMENT_FORMAT_VERSION
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if document.save_slice_id.trim().is_empty() {
|
||||||
|
return Err("save_slice_id must not be empty".to_string());
|
||||||
|
}
|
||||||
|
if document
|
||||||
|
.source
|
||||||
|
.description
|
||||||
|
.as_deref()
|
||||||
|
.is_some_and(|text| text.trim().is_empty())
|
||||||
|
{
|
||||||
|
return Err("save slice source.description must not be empty".to_string());
|
||||||
|
}
|
||||||
|
if document
|
||||||
|
.source
|
||||||
|
.original_save_filename
|
||||||
|
.as_deref()
|
||||||
|
.is_some_and(|text| text.trim().is_empty())
|
||||||
|
{
|
||||||
|
return Err("save slice source.original_save_filename must not be empty".to_string());
|
||||||
|
}
|
||||||
|
if document
|
||||||
|
.source
|
||||||
|
.original_save_sha256
|
||||||
|
.as_deref()
|
||||||
|
.is_some_and(|text| text.trim().is_empty())
|
||||||
|
{
|
||||||
|
return Err("save slice source.original_save_sha256 must not be empty".to_string());
|
||||||
|
}
|
||||||
|
for (index, note) in document.source.notes.iter().enumerate() {
|
||||||
|
if note.trim().is_empty() {
|
||||||
|
return Err(format!(
|
||||||
|
"save slice source.notes[{index}] must not be empty"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if document.save_slice.mechanism_family.trim().is_empty() {
|
||||||
|
return Err("save_slice.mechanism_family must not be empty".to_string());
|
||||||
|
}
|
||||||
|
if document.save_slice.mechanism_confidence.trim().is_empty() {
|
||||||
|
return Err("save_slice.mechanism_confidence must not be empty".to_string());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_runtime_save_slice_document(
|
||||||
|
path: &Path,
|
||||||
|
) -> Result<RuntimeSaveSliceDocument, Box<dyn std::error::Error>> {
|
||||||
|
let text = std::fs::read_to_string(path)?;
|
||||||
|
let document: RuntimeSaveSliceDocument = serde_json::from_str(&text)?;
|
||||||
|
Ok(document)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_runtime_save_slice_document(
|
||||||
|
path: &Path,
|
||||||
|
document: &RuntimeSaveSliceDocument,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
validate_runtime_save_slice_document(document)
|
||||||
|
.map_err(|err| format!("invalid runtime save slice document: {err}"))?;
|
||||||
|
let bytes = serde_json::to_vec_pretty(document)?;
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
std::fs::create_dir_all(parent)?;
|
||||||
|
}
|
||||||
|
std::fs::write(path, bytes)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn load_runtime_state_import(
|
pub fn load_runtime_state_import(
|
||||||
path: &Path,
|
path: &Path,
|
||||||
) -> Result<RuntimeStateImport, Box<dyn std::error::Error>> {
|
) -> Result<RuntimeStateImport, Box<dyn std::error::Error>> {
|
||||||
|
|
@ -607,6 +703,28 @@ pub fn load_runtime_state_import_from_str(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Ok(document) = serde_json::from_str::<RuntimeSaveSliceDocument>(text) {
|
||||||
|
validate_runtime_save_slice_document(&document)
|
||||||
|
.map_err(|err| format!("invalid runtime save slice document: {err}"))?;
|
||||||
|
let mut description_parts = Vec::new();
|
||||||
|
if let Some(description) = document.source.description {
|
||||||
|
description_parts.push(description);
|
||||||
|
}
|
||||||
|
if let Some(filename) = document.source.original_save_filename {
|
||||||
|
description_parts.push(format!("source save {filename}"));
|
||||||
|
}
|
||||||
|
let import = project_save_slice_to_runtime_state_import(
|
||||||
|
&document.save_slice,
|
||||||
|
&document.save_slice_id,
|
||||||
|
if description_parts.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(description_parts.join(" | "))
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
return Ok(import);
|
||||||
|
}
|
||||||
|
|
||||||
let state: RuntimeState = serde_json::from_str(text)?;
|
let state: RuntimeState = serde_json::from_str(text)?;
|
||||||
state
|
state
|
||||||
.validate()
|
.validate()
|
||||||
|
|
@ -713,6 +831,84 @@ mod tests {
|
||||||
assert!(import.description.is_none());
|
assert!(import.description.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validates_and_roundtrips_save_slice_document() {
|
||||||
|
let document = RuntimeSaveSliceDocument {
|
||||||
|
format_version: SAVE_SLICE_DOCUMENT_FORMAT_VERSION,
|
||||||
|
save_slice_id: "save-slice-smoke".to_string(),
|
||||||
|
source: RuntimeSaveSliceDocumentSource {
|
||||||
|
description: Some("test save slice".to_string()),
|
||||||
|
original_save_filename: Some("smoke.gms".to_string()),
|
||||||
|
original_save_sha256: Some("deadbeef".to_string()),
|
||||||
|
notes: vec!["captured fixture".to_string()],
|
||||||
|
},
|
||||||
|
save_slice: crate::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: None,
|
||||||
|
notes: vec![],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
assert!(validate_runtime_save_slice_document(&document).is_ok());
|
||||||
|
|
||||||
|
let nonce = std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.expect("system time should be after epoch")
|
||||||
|
.as_nanos();
|
||||||
|
let path = std::env::temp_dir().join(format!("rrt-save-slice-doc-{nonce}.json"));
|
||||||
|
save_runtime_save_slice_document(&path, &document).expect("save slice doc should save");
|
||||||
|
let loaded = load_runtime_save_slice_document(&path).expect("save slice doc should load");
|
||||||
|
assert_eq!(document, loaded);
|
||||||
|
let _ = std::fs::remove_file(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn loads_save_slice_document_as_runtime_state_import() {
|
||||||
|
let text = serde_json::to_string(&RuntimeSaveSliceDocument {
|
||||||
|
format_version: SAVE_SLICE_DOCUMENT_FORMAT_VERSION,
|
||||||
|
save_slice_id: "save-slice-import".to_string(),
|
||||||
|
source: RuntimeSaveSliceDocumentSource {
|
||||||
|
description: Some("test save slice import".to_string()),
|
||||||
|
original_save_filename: Some("import.gms".to_string()),
|
||||||
|
original_save_sha256: None,
|
||||||
|
notes: vec![],
|
||||||
|
},
|
||||||
|
save_slice: crate::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: None,
|
||||||
|
notes: vec![],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.expect("save slice doc should serialize");
|
||||||
|
|
||||||
|
let import = load_runtime_state_import_from_str(&text, "fallback")
|
||||||
|
.expect("save slice document should load as runtime import");
|
||||||
|
assert_eq!(import.import_id, "save-slice-import");
|
||||||
|
assert_eq!(
|
||||||
|
import
|
||||||
|
.state
|
||||||
|
.metadata
|
||||||
|
.get("save_slice.import_projection")
|
||||||
|
.map(String::as_str),
|
||||||
|
Some("partial-runtime-restore-v1")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn projects_save_slice_into_runtime_state_import() {
|
fn projects_save_slice_into_runtime_state_import() {
|
||||||
let save_slice = SmpLoadedSaveSlice {
|
let save_slice = SmpLoadedSaveSlice {
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,11 @@ pub use campaign_exe::{
|
||||||
OBSERVED_CAMPAIGN_SCENARIO_NAMES, inspect_campaign_exe_bytes, inspect_campaign_exe_file,
|
OBSERVED_CAMPAIGN_SCENARIO_NAMES, inspect_campaign_exe_bytes, inspect_campaign_exe_file,
|
||||||
};
|
};
|
||||||
pub use import::{
|
pub use import::{
|
||||||
RuntimeStateDumpDocument, RuntimeStateDumpSource, RuntimeStateImport,
|
RuntimeSaveSliceDocument, RuntimeSaveSliceDocumentSource, RuntimeStateDumpDocument,
|
||||||
STATE_DUMP_FORMAT_VERSION, load_runtime_state_import,
|
RuntimeStateDumpSource, RuntimeStateImport, SAVE_SLICE_DOCUMENT_FORMAT_VERSION,
|
||||||
project_save_slice_to_runtime_state_import, validate_runtime_state_dump_document,
|
STATE_DUMP_FORMAT_VERSION, load_runtime_save_slice_document, load_runtime_state_import,
|
||||||
|
project_save_slice_to_runtime_state_import, save_runtime_save_slice_document,
|
||||||
|
validate_runtime_save_slice_document, validate_runtime_state_dump_document,
|
||||||
};
|
};
|
||||||
pub use persistence::{
|
pub use persistence::{
|
||||||
RuntimeSnapshotDocument, RuntimeSnapshotSource, SNAPSHOT_FORMAT_VERSION,
|
RuntimeSnapshotDocument, RuntimeSnapshotSource, SNAPSHOT_FORMAT_VERSION,
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ pub struct RuntimeSummary {
|
||||||
pub packed_event_record_count: usize,
|
pub packed_event_record_count: usize,
|
||||||
pub packed_event_decoded_record_count: usize,
|
pub packed_event_decoded_record_count: usize,
|
||||||
pub packed_event_imported_runtime_record_count: usize,
|
pub packed_event_imported_runtime_record_count: usize,
|
||||||
|
pub packed_event_parity_only_record_count: usize,
|
||||||
|
pub packed_event_unsupported_record_count: usize,
|
||||||
pub event_runtime_record_count: usize,
|
pub event_runtime_record_count: usize,
|
||||||
pub candidate_availability_count: usize,
|
pub candidate_availability_count: usize,
|
||||||
pub zero_candidate_availability_count: usize,
|
pub zero_candidate_availability_count: usize,
|
||||||
|
|
@ -129,6 +131,28 @@ impl RuntimeSummary {
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|summary| summary.imported_runtime_record_count)
|
.map(|summary| summary.imported_runtime_record_count)
|
||||||
.unwrap_or(0),
|
.unwrap_or(0),
|
||||||
|
packed_event_parity_only_record_count: state
|
||||||
|
.packed_event_collection
|
||||||
|
.as_ref()
|
||||||
|
.map(|summary| {
|
||||||
|
summary
|
||||||
|
.records
|
||||||
|
.iter()
|
||||||
|
.filter(|record| record.decode_status == "parity_only")
|
||||||
|
.count()
|
||||||
|
})
|
||||||
|
.unwrap_or(0),
|
||||||
|
packed_event_unsupported_record_count: state
|
||||||
|
.packed_event_collection
|
||||||
|
.as_ref()
|
||||||
|
.map(|summary| {
|
||||||
|
summary
|
||||||
|
.records
|
||||||
|
.iter()
|
||||||
|
.filter(|record| record.decode_status == "unsupported_framing")
|
||||||
|
.count()
|
||||||
|
})
|
||||||
|
.unwrap_or(0),
|
||||||
event_runtime_record_count: state.event_runtime_records.len(),
|
event_runtime_record_count: state.event_runtime_records.len(),
|
||||||
candidate_availability_count: state.candidate_availability.len(),
|
candidate_availability_count: state.candidate_availability.len(),
|
||||||
zero_candidate_availability_count: state
|
zero_candidate_availability_count: state
|
||||||
|
|
|
||||||
|
|
@ -67,17 +67,17 @@ Current local tool status:
|
||||||
The atlas milestone is broad enough that the next implementation focus has already shifted downward
|
The atlas milestone is broad enough that the next implementation focus has already shifted downward
|
||||||
into runtime rehosting. The current runtime baseline now includes deterministic stepping, periodic
|
into runtime rehosting. The current runtime baseline now includes deterministic stepping, periodic
|
||||||
trigger dispatch, normalized runtime effects, staged event-record mutation, fixture execution,
|
trigger dispatch, normalized runtime effects, staged event-record mutation, fixture execution,
|
||||||
state-diff tooling, and a packed-event persistence bridge that now reaches per-record summaries and
|
state-diff tooling, tracked save-slice documents for captured-runtime inputs, and a packed-event
|
||||||
selective executable import.
|
persistence bridge that now reaches per-record summaries and selective executable import.
|
||||||
|
|
||||||
The highest-value next passes are now:
|
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
|
||||||
- deepen captured-runtime and round-trip fixture coverage on top of the packed-event bridge that now
|
|
||||||
exists
|
|
||||||
- widen packed-event target-family coverage only where static evidence is strong enough to support
|
- widen packed-event target-family coverage only where static evidence is strong enough to support
|
||||||
deterministic executable import
|
deterministic executable import
|
||||||
|
- add the next imported object/context families needed to turn current parity-only packed rows into
|
||||||
|
executable runtime records
|
||||||
- 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
|
||||||
environment
|
environment
|
||||||
- keep `docs/runtime-rehost-plan.md` current as the runtime baseline and next implementation slice
|
- keep `docs/runtime-rehost-plan.md` current as the runtime baseline and next implementation slice
|
||||||
|
|
|
||||||
|
|
@ -22,12 +22,12 @@ Implemented today:
|
||||||
- snapshots, state dumps, save-slice projection, and normalized state diffing already exist in the
|
- snapshots, state dumps, save-slice projection, and normalized state diffing already exist in the
|
||||||
CLI and fixture layers
|
CLI and fixture layers
|
||||||
- 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, normalized state-fragment assertions, and imported packed-event
|
service, snapshot-backed inputs, save-slice-backed inputs, normalized state-fragment assertions,
|
||||||
execution
|
and imported packed-event execution
|
||||||
|
|
||||||
That means the next implementation work is breadth, not bootstrap. The recommended next slice is
|
That means the next implementation work is breadth, not bootstrap. The recommended next slice is
|
||||||
captured-runtime depth plus wider packed-event target-family coverage, not another persistence
|
wider packed-event target-family coverage plus company-collection import depth, not another
|
||||||
scaffold pass.
|
persistence scaffold pass.
|
||||||
|
|
||||||
## Why This Boundary
|
## Why This Boundary
|
||||||
|
|
||||||
|
|
@ -220,8 +220,10 @@ Current status:
|
||||||
projected runtime snapshots, normalized diffs, and fixtures
|
projected runtime snapshots, normalized diffs, and fixtures
|
||||||
- the first decoded packed-event subset can now import into executable runtime records when the
|
- the first decoded packed-event subset can now import into executable runtime records when the
|
||||||
decoded actions fit the current normalized runtime-effect model
|
decoded actions fit the current normalized runtime-effect model
|
||||||
- the remaining gap is broader captured-runtime and round-trip fixture depth plus wider packed
|
- tracked save-slice documents now provide a repo-friendly captured-runtime path without checking in
|
||||||
target-family coverage, not first-pass packed-event decode
|
raw `.smp` binaries
|
||||||
|
- the remaining gap is wider packed target-family coverage plus company-import depth, not
|
||||||
|
first-pass captured-runtime plumbing
|
||||||
|
|
||||||
### Milestone 4: Domain Expansion
|
### Milestone 4: Domain Expansion
|
||||||
|
|
||||||
|
|
@ -352,6 +354,7 @@ The currently implemented normalized runtime surface is:
|
||||||
normalized runtime-effect vocabulary with staged event-record mutation
|
normalized runtime-effect vocabulary with staged event-record mutation
|
||||||
- save-side inspection and partial state projection for `.smp` inputs, including per-record packed
|
- save-side inspection and partial state projection for `.smp` inputs, including per-record packed
|
||||||
event summaries and selective executable import
|
event summaries and selective executable import
|
||||||
|
- tracked save-slice documents plus save-slice-backed fixture loading for captured-runtime coverage
|
||||||
|
|
||||||
Checked-in fixture families already include:
|
Checked-in fixture families already include:
|
||||||
|
|
||||||
|
|
@ -363,30 +366,30 @@ Checked-in fixture families already include:
|
||||||
|
|
||||||
## Next Slice
|
## Next Slice
|
||||||
|
|
||||||
The recommended next implementation slice is broader captured-runtime depth on top of the packed
|
The recommended next implementation slice is wider packed-event target-family coverage on top of the
|
||||||
event bridge that now exists today.
|
captured save-slice workflow that now exists today.
|
||||||
|
|
||||||
Target behavior:
|
Target behavior:
|
||||||
|
|
||||||
- keep the packed event bridge grounded against real captured save inputs rather than only synthetic
|
|
||||||
parser tests and snapshot fixtures
|
|
||||||
- expand the executable import subset beyond the current direct-state and follow-on lanes only when
|
- expand the executable import subset beyond the current direct-state and follow-on lanes only when
|
||||||
target resolution and field semantics are statically grounded enough to preserve headless
|
target resolution and field semantics are statically grounded enough to preserve headless
|
||||||
determinism
|
determinism
|
||||||
|
- add the next imported object families needed to make currently parity-only packed rows executable,
|
||||||
|
starting with company-targeted effects
|
||||||
- keep preserving unsupported packed rows as parity summaries instead of guessing executable meaning
|
- keep preserving unsupported packed rows as parity summaries instead of guessing executable meaning
|
||||||
|
|
||||||
Public-model additions for that slice:
|
Public-model additions for that slice:
|
||||||
|
|
||||||
- additional captured-save fixture material for packed event collections
|
|
||||||
- wider target-family summaries only where imported execution can be justified by current static
|
- wider target-family summaries only where imported execution can be justified by current static
|
||||||
evidence
|
evidence
|
||||||
|
- imported company/runtime context needed by the next packed-event target families
|
||||||
- no shell queue/modal behavior in the runtime core
|
- no shell queue/modal behavior in the runtime core
|
||||||
|
|
||||||
Fixture work for that slice:
|
Fixture work for that slice:
|
||||||
|
|
||||||
- captured `.smp` or save-slice-backed fixtures that prove real packed event records survive import
|
- save-slice-backed fixtures that prove real packed event records survive import and diff paths
|
||||||
and diff paths
|
- regression fixtures that lock the current selective executable import boundary and the
|
||||||
- regression fixtures that lock the current selective executable import boundary
|
unsupported/parity-only counts
|
||||||
- state-fragment assertions that lock both packed parity summaries and imported executable records
|
- state-fragment assertions that lock both packed parity summaries and imported executable records
|
||||||
|
|
||||||
Do not mix this slice with:
|
Do not mix this slice with:
|
||||||
|
|
|
||||||
51
fixtures/runtime/packed-event-parity-save-slice-fixture.json
Normal file
51
fixtures/runtime/packed-event-parity-save-slice-fixture.json
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"fixture_id": "packed-event-parity-save-slice-fixture",
|
||||||
|
"source": {
|
||||||
|
"kind": "captured-runtime",
|
||||||
|
"description": "Fixture backed by a tracked save-slice document with parity-heavy packed-event records."
|
||||||
|
},
|
||||||
|
"state_save_slice_path": "packed-event-parity-save-slice.json",
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"kind": "step_count",
|
||||||
|
"steps": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"expected_summary": {
|
||||||
|
"calendar": {
|
||||||
|
"year": 1830,
|
||||||
|
"month_slot": 0,
|
||||||
|
"phase_slot": 0,
|
||||||
|
"tick_slot": 1
|
||||||
|
},
|
||||||
|
"calendar_projection_is_placeholder": true,
|
||||||
|
"packed_event_collection_present": true,
|
||||||
|
"packed_event_record_count": 2,
|
||||||
|
"packed_event_decoded_record_count": 1,
|
||||||
|
"packed_event_imported_runtime_record_count": 0,
|
||||||
|
"packed_event_parity_only_record_count": 1,
|
||||||
|
"packed_event_unsupported_record_count": 1,
|
||||||
|
"event_runtime_record_count": 0,
|
||||||
|
"total_company_cash": 0
|
||||||
|
},
|
||||||
|
"expected_state_fragment": {
|
||||||
|
"calendar": {
|
||||||
|
"tick_slot": 1
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"save_slice.import_projection": "partial-runtime-restore-v1"
|
||||||
|
},
|
||||||
|
"packed_event_collection": {
|
||||||
|
"live_entry_ids": [3, 5],
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"decode_status": "unsupported_framing"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"decode_status": "parity_only"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
123
fixtures/runtime/packed-event-parity-save-slice.json
Normal file
123
fixtures/runtime/packed-event-parity-save-slice.json
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"save_slice_id": "packed-event-parity-save-slice",
|
||||||
|
"source": {
|
||||||
|
"description": "Tracked save-slice document representing a parity-heavy captured packed-event collection.",
|
||||||
|
"original_save_filename": "captured-parity.gms",
|
||||||
|
"original_save_sha256": "parity-sample-sha256",
|
||||||
|
"notes": [
|
||||||
|
"tracked as JSON save-slice document rather than raw .smp",
|
||||||
|
"preserves one unsupported row and one decoded-but-parity-only row"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"save_slice": {
|
||||||
|
"file_extension_hint": "gms",
|
||||||
|
"container_profile_family": "rt3-classic-save-container-v1",
|
||||||
|
"mechanism_family": "classic-save-rehydrate-v1",
|
||||||
|
"mechanism_confidence": "grounded",
|
||||||
|
"trailer_family": null,
|
||||||
|
"bridge_family": null,
|
||||||
|
"profile": null,
|
||||||
|
"candidate_availability_table": null,
|
||||||
|
"special_conditions_table": null,
|
||||||
|
"event_runtime_collection": {
|
||||||
|
"source_kind": "packed-event-runtime-collection",
|
||||||
|
"mechanism_family": "classic-save-rehydrate-v1",
|
||||||
|
"mechanism_confidence": "grounded",
|
||||||
|
"container_profile_family": "rt3-classic-save-container-v1",
|
||||||
|
"metadata_tag_offset": 28928,
|
||||||
|
"records_tag_offset": 29184,
|
||||||
|
"close_tag_offset": 29696,
|
||||||
|
"packed_state_version": 1001,
|
||||||
|
"packed_state_version_hex": "0x000003e9",
|
||||||
|
"live_id_bound": 5,
|
||||||
|
"live_record_count": 2,
|
||||||
|
"live_entry_ids": [3, 5],
|
||||||
|
"decoded_record_count": 1,
|
||||||
|
"imported_runtime_record_count": 0,
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"record_index": 0,
|
||||||
|
"live_entry_id": 3,
|
||||||
|
"payload_offset": 29186,
|
||||||
|
"payload_len": 96,
|
||||||
|
"decode_status": "unsupported_framing",
|
||||||
|
"grouped_effect_row_counts": [0, 0, 0, 0],
|
||||||
|
"decoded_actions": [],
|
||||||
|
"executable_import_ready": false,
|
||||||
|
"notes": [
|
||||||
|
"real payload framing not yet decoded"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"record_index": 1,
|
||||||
|
"live_entry_id": 5,
|
||||||
|
"payload_offset": 29290,
|
||||||
|
"payload_len": 72,
|
||||||
|
"decode_status": "parity_only",
|
||||||
|
"trigger_kind": 7,
|
||||||
|
"active": true,
|
||||||
|
"marks_collection_dirty": false,
|
||||||
|
"one_shot": false,
|
||||||
|
"text_bands": [
|
||||||
|
{
|
||||||
|
"label": "primary_text_band",
|
||||||
|
"packed_len": 7,
|
||||||
|
"present": true,
|
||||||
|
"preview": "Parity!"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "secondary_text_band_0",
|
||||||
|
"packed_len": 0,
|
||||||
|
"present": false,
|
||||||
|
"preview": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "secondary_text_band_1",
|
||||||
|
"packed_len": 0,
|
||||||
|
"present": false,
|
||||||
|
"preview": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "secondary_text_band_2",
|
||||||
|
"packed_len": 0,
|
||||||
|
"present": false,
|
||||||
|
"preview": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "secondary_text_band_3",
|
||||||
|
"packed_len": 0,
|
||||||
|
"present": false,
|
||||||
|
"preview": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "secondary_text_band_4",
|
||||||
|
"packed_len": 0,
|
||||||
|
"present": false,
|
||||||
|
"preview": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"standalone_condition_row_count": 0,
|
||||||
|
"grouped_effect_row_counts": [0, 0, 0, 0],
|
||||||
|
"decoded_actions": [
|
||||||
|
{
|
||||||
|
"kind": "adjust_company_cash",
|
||||||
|
"target": {
|
||||||
|
"kind": "ids",
|
||||||
|
"ids": [42]
|
||||||
|
},
|
||||||
|
"delta": 75
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"executable_import_ready": false,
|
||||||
|
"notes": [
|
||||||
|
"decoded action requires explicit imported company ids before execution"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notes": [
|
||||||
|
"parity-heavy packed-event sample"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"fixture_id": "packed-event-selective-import-save-slice-fixture",
|
||||||
|
"source": {
|
||||||
|
"kind": "captured-runtime",
|
||||||
|
"description": "Fixture backed by a tracked save-slice document with one imported packed-event record and one parity-only record."
|
||||||
|
},
|
||||||
|
"state_save_slice_path": "packed-event-selective-import-save-slice.json",
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"kind": "service_trigger_kind",
|
||||||
|
"trigger_kind": 7
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"expected_summary": {
|
||||||
|
"calendar_projection_is_placeholder": true,
|
||||||
|
"packed_event_collection_present": true,
|
||||||
|
"packed_event_record_count": 2,
|
||||||
|
"packed_event_decoded_record_count": 2,
|
||||||
|
"packed_event_imported_runtime_record_count": 1,
|
||||||
|
"packed_event_parity_only_record_count": 1,
|
||||||
|
"packed_event_unsupported_record_count": 0,
|
||||||
|
"event_runtime_record_count": 2,
|
||||||
|
"special_condition_count": 1,
|
||||||
|
"enabled_special_condition_count": 1,
|
||||||
|
"total_event_record_service_count": 2,
|
||||||
|
"total_trigger_dispatch_count": 2,
|
||||||
|
"dirty_rerun_count": 1,
|
||||||
|
"total_company_cash": 0
|
||||||
|
},
|
||||||
|
"expected_state_fragment": {
|
||||||
|
"world_flags": {
|
||||||
|
"from_packed_root": true
|
||||||
|
},
|
||||||
|
"special_conditions": {
|
||||||
|
"Imported Follow-On": 1
|
||||||
|
},
|
||||||
|
"packed_event_collection": {
|
||||||
|
"live_entry_ids": [7, 9],
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"decode_status": "executable",
|
||||||
|
"executable_import_ready": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"decode_status": "parity_only",
|
||||||
|
"executable_import_ready": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"event_runtime_records": [
|
||||||
|
{
|
||||||
|
"record_id": 7,
|
||||||
|
"service_count": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"record_id": 99,
|
||||||
|
"service_count": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"service_state": {
|
||||||
|
"dirty_rerun_count": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
189
fixtures/runtime/packed-event-selective-import-save-slice.json
Normal file
189
fixtures/runtime/packed-event-selective-import-save-slice.json
Normal file
|
|
@ -0,0 +1,189 @@
|
||||||
|
{
|
||||||
|
"format_version": 1,
|
||||||
|
"save_slice_id": "packed-event-selective-import-save-slice",
|
||||||
|
"source": {
|
||||||
|
"description": "Tracked save-slice document representing one executable packed-event record plus one parity-only record.",
|
||||||
|
"original_save_filename": "captured-selective-import.gms",
|
||||||
|
"original_save_sha256": "selective-import-sample-sha256",
|
||||||
|
"notes": [
|
||||||
|
"tracked as JSON save-slice document rather than raw .smp",
|
||||||
|
"locks the current selective import boundary"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"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": 9,
|
||||||
|
"live_record_count": 2,
|
||||||
|
"live_entry_ids": [7, 9],
|
||||||
|
"decoded_record_count": 2,
|
||||||
|
"imported_runtime_record_count": 1,
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"record_index": 0,
|
||||||
|
"live_entry_id": 7,
|
||||||
|
"payload_offset": 29186,
|
||||||
|
"payload_len": 64,
|
||||||
|
"decode_status": "executable",
|
||||||
|
"trigger_kind": 7,
|
||||||
|
"active": true,
|
||||||
|
"marks_collection_dirty": true,
|
||||||
|
"one_shot": false,
|
||||||
|
"text_bands": [
|
||||||
|
{
|
||||||
|
"label": "primary_text_band",
|
||||||
|
"packed_len": 5,
|
||||||
|
"present": true,
|
||||||
|
"preview": "Alpha"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "secondary_text_band_0",
|
||||||
|
"packed_len": 0,
|
||||||
|
"present": false,
|
||||||
|
"preview": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "secondary_text_band_1",
|
||||||
|
"packed_len": 0,
|
||||||
|
"present": false,
|
||||||
|
"preview": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "secondary_text_band_2",
|
||||||
|
"packed_len": 0,
|
||||||
|
"present": false,
|
||||||
|
"preview": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "secondary_text_band_3",
|
||||||
|
"packed_len": 0,
|
||||||
|
"present": false,
|
||||||
|
"preview": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "secondary_text_band_4",
|
||||||
|
"packed_len": 0,
|
||||||
|
"present": false,
|
||||||
|
"preview": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"standalone_condition_row_count": 1,
|
||||||
|
"grouped_effect_row_counts": [0, 1, 0, 0],
|
||||||
|
"decoded_actions": [
|
||||||
|
{
|
||||||
|
"kind": "set_world_flag",
|
||||||
|
"key": "from_packed_root",
|
||||||
|
"value": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "append_event_record",
|
||||||
|
"record": {
|
||||||
|
"record_id": 99,
|
||||||
|
"trigger_kind": 10,
|
||||||
|
"active": true,
|
||||||
|
"marks_collection_dirty": false,
|
||||||
|
"one_shot": false,
|
||||||
|
"effects": [
|
||||||
|
{
|
||||||
|
"kind": "set_special_condition",
|
||||||
|
"label": "Imported Follow-On",
|
||||||
|
"value": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"executable_import_ready": true,
|
||||||
|
"notes": [
|
||||||
|
"fixture packed-event record"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"record_index": 1,
|
||||||
|
"live_entry_id": 9,
|
||||||
|
"payload_offset": 29260,
|
||||||
|
"payload_len": 72,
|
||||||
|
"decode_status": "parity_only",
|
||||||
|
"trigger_kind": 7,
|
||||||
|
"active": true,
|
||||||
|
"marks_collection_dirty": false,
|
||||||
|
"one_shot": false,
|
||||||
|
"text_bands": [
|
||||||
|
{
|
||||||
|
"label": "primary_text_band",
|
||||||
|
"packed_len": 4,
|
||||||
|
"present": true,
|
||||||
|
"preview": "Beta"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "secondary_text_band_0",
|
||||||
|
"packed_len": 0,
|
||||||
|
"present": false,
|
||||||
|
"preview": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "secondary_text_band_1",
|
||||||
|
"packed_len": 0,
|
||||||
|
"present": false,
|
||||||
|
"preview": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "secondary_text_band_2",
|
||||||
|
"packed_len": 0,
|
||||||
|
"present": false,
|
||||||
|
"preview": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "secondary_text_band_3",
|
||||||
|
"packed_len": 0,
|
||||||
|
"present": false,
|
||||||
|
"preview": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "secondary_text_band_4",
|
||||||
|
"packed_len": 0,
|
||||||
|
"present": false,
|
||||||
|
"preview": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"standalone_condition_row_count": 0,
|
||||||
|
"grouped_effect_row_counts": [0, 0, 0, 0],
|
||||||
|
"decoded_actions": [
|
||||||
|
{
|
||||||
|
"kind": "adjust_company_cash",
|
||||||
|
"target": {
|
||||||
|
"kind": "ids",
|
||||||
|
"ids": [42]
|
||||||
|
},
|
||||||
|
"delta": 50
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"executable_import_ready": false,
|
||||||
|
"notes": [
|
||||||
|
"decoded action still requires company import depth"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notes": [
|
||||||
|
"mixed packed-event sample"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue