Recover whole-game packed event conditions

This commit is contained in:
Jan Petykiewicz 2026-04-15 22:38:13 -07:00
commit 5d779263d2
7 changed files with 414 additions and 117 deletions

View file

@ -27,7 +27,10 @@ train roster and opaque economic-status lane needed for real descriptors `8` `Ec
`Territory - Allow All` now executes too, reinterpreted as company-to-territory access rights `Territory - Allow All` now executes too, reinterpreted as company-to-territory access rights
rather than a territory-owned policy bit. Whole-game ordinary-condition execution now exists too: rather than a territory-owned policy bit. Whole-game ordinary-condition execution now exists too:
special-condition thresholds, candidate-availability thresholds, and economic-status-code special-condition thresholds, candidate-availability thresholds, and economic-status-code
thresholds now gate imported runtime records through the same service path, and checked-in thresholds now gate imported runtime records through the same service path, and that world-side
condition batch now decodes from checked-in metadata instead of fixture-only ids: real
special-condition label ids, real economic-status ids, and the recovered `%1 Avail.` candidate
template plus candidate-name side strings all lower into the runtime condition model. Checked-in
whole-game descriptor metadata now drives the first real world-side effect batch too: whole-game descriptor metadata now drives the first real world-side effect batch too:
special-condition and candidate-availability setters import natively while world-flag rows remain special-condition and candidate-availability setters import natively while world-flag rows remain
parity-only until keyed mapping is grounded. Explicit unmapped world-condition and parity-only until keyed mapping is grounded. Explicit unmapped world-condition and

View file

@ -214,127 +214,192 @@ enum RealOrdinaryConditionMetric {
CompanyTerritory(RuntimeTrackMetric), CompanyTerritory(RuntimeTrackMetric),
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum RealWorldConditionKind {
SpecialCondition { label: &'static str },
CandidateAvailability,
EconomicStatus,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum RealOrdinaryConditionKind {
Numeric(RealOrdinaryConditionMetric),
WorldState(RealWorldConditionKind),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct RealOrdinaryConditionMetadata { struct RealOrdinaryConditionMetadata {
raw_condition_id: i32, raw_condition_id: i32,
label: &'static str, label: &'static str,
metric: RealOrdinaryConditionMetric, kind: RealOrdinaryConditionKind,
} }
const REAL_ORDINARY_CONDITION_METADATA: [RealOrdinaryConditionMetadata; 22] = [ const REAL_CANDIDATE_AVAILABILITY_CONDITION_TEMPLATE_ID: i32 = 435;
const REAL_ORDINARY_CONDITION_METADATA: [RealOrdinaryConditionMetadata; 24] = [
RealOrdinaryConditionMetadata { RealOrdinaryConditionMetadata {
raw_condition_id: 1802, raw_condition_id: 1802,
label: "Current Cash", label: "Current Cash",
metric: RealOrdinaryConditionMetric::Company(RuntimeCompanyMetric::CurrentCash), kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
RuntimeCompanyMetric::CurrentCash,
)),
}, },
RealOrdinaryConditionMetadata { RealOrdinaryConditionMetadata {
raw_condition_id: 951, raw_condition_id: 951,
label: "Total Debt", label: "Total Debt",
metric: RealOrdinaryConditionMetric::Company(RuntimeCompanyMetric::TotalDebt), kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
RuntimeCompanyMetric::TotalDebt,
)),
}, },
RealOrdinaryConditionMetadata { RealOrdinaryConditionMetadata {
raw_condition_id: 2366, raw_condition_id: 2366,
label: "Credit Rating", label: "Credit Rating",
metric: RealOrdinaryConditionMetric::Company(RuntimeCompanyMetric::CreditRating), kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
RuntimeCompanyMetric::CreditRating,
)),
}, },
RealOrdinaryConditionMetadata { RealOrdinaryConditionMetadata {
raw_condition_id: 2368, raw_condition_id: 2368,
label: "Prime Rate", label: "Prime Rate",
metric: RealOrdinaryConditionMetric::Company(RuntimeCompanyMetric::PrimeRate), kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
RuntimeCompanyMetric::PrimeRate,
)),
}, },
RealOrdinaryConditionMetadata { RealOrdinaryConditionMetadata {
raw_condition_id: 2293, raw_condition_id: 2293,
label: "Company Track Pieces", label: "Company Track Pieces",
metric: RealOrdinaryConditionMetric::Company(RuntimeCompanyMetric::TrackPiecesTotal), kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
RuntimeCompanyMetric::TrackPiecesTotal,
)),
}, },
RealOrdinaryConditionMetadata { RealOrdinaryConditionMetadata {
raw_condition_id: 2294, raw_condition_id: 2294,
label: "Company Single Track Pieces", label: "Company Single Track Pieces",
metric: RealOrdinaryConditionMetric::Company(RuntimeCompanyMetric::TrackPiecesSingle), kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
RuntimeCompanyMetric::TrackPiecesSingle,
)),
}, },
RealOrdinaryConditionMetadata { RealOrdinaryConditionMetadata {
raw_condition_id: 2295, raw_condition_id: 2295,
label: "Company Double Track Pieces", label: "Company Double Track Pieces",
metric: RealOrdinaryConditionMetric::Company(RuntimeCompanyMetric::TrackPiecesDouble), kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
RuntimeCompanyMetric::TrackPiecesDouble,
)),
}, },
RealOrdinaryConditionMetadata { RealOrdinaryConditionMetadata {
raw_condition_id: 2296, raw_condition_id: 2296,
label: "Company Transition Track Pieces", label: "Company Transition Track Pieces",
metric: RealOrdinaryConditionMetric::Company(RuntimeCompanyMetric::TrackPiecesTransition), kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
RuntimeCompanyMetric::TrackPiecesTransition,
)),
}, },
RealOrdinaryConditionMetadata { RealOrdinaryConditionMetadata {
raw_condition_id: 2297, raw_condition_id: 2297,
label: "Company Electric Track Pieces", label: "Company Electric Track Pieces",
metric: RealOrdinaryConditionMetric::Company(RuntimeCompanyMetric::TrackPiecesElectric), kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
RuntimeCompanyMetric::TrackPiecesElectric,
)),
}, },
RealOrdinaryConditionMetadata { RealOrdinaryConditionMetadata {
raw_condition_id: 2298, raw_condition_id: 2298,
label: "Company Non-Electric Track Pieces", label: "Company Non-Electric Track Pieces",
metric: RealOrdinaryConditionMetric::Company(RuntimeCompanyMetric::TrackPiecesNonElectric), kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
RuntimeCompanyMetric::TrackPiecesNonElectric,
)),
}, },
RealOrdinaryConditionMetadata { RealOrdinaryConditionMetadata {
raw_condition_id: 2313, raw_condition_id: 2313,
label: "Territory Track Pieces", label: "Territory Track Pieces",
metric: RealOrdinaryConditionMetric::Territory(RuntimeTerritoryMetric::TrackPiecesTotal), kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Territory(
RuntimeTerritoryMetric::TrackPiecesTotal,
)),
}, },
RealOrdinaryConditionMetadata { RealOrdinaryConditionMetadata {
raw_condition_id: 2314, raw_condition_id: 2314,
label: "Territory Single Track Pieces", label: "Territory Single Track Pieces",
metric: RealOrdinaryConditionMetric::Territory(RuntimeTerritoryMetric::TrackPiecesSingle), kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Territory(
RuntimeTerritoryMetric::TrackPiecesSingle,
)),
}, },
RealOrdinaryConditionMetadata { RealOrdinaryConditionMetadata {
raw_condition_id: 2315, raw_condition_id: 2315,
label: "Territory Double Track Pieces", label: "Territory Double Track Pieces",
metric: RealOrdinaryConditionMetric::Territory(RuntimeTerritoryMetric::TrackPiecesDouble), kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Territory(
RuntimeTerritoryMetric::TrackPiecesDouble,
)),
}, },
RealOrdinaryConditionMetadata { RealOrdinaryConditionMetadata {
raw_condition_id: 2316, raw_condition_id: 2316,
label: "Territory Transition Track Pieces", label: "Territory Transition Track Pieces",
metric: RealOrdinaryConditionMetric::Territory( kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Territory(
RuntimeTerritoryMetric::TrackPiecesTransition, RuntimeTerritoryMetric::TrackPiecesTransition,
), )),
}, },
RealOrdinaryConditionMetadata { RealOrdinaryConditionMetadata {
raw_condition_id: 2317, raw_condition_id: 2317,
label: "Territory Electric Track Pieces", label: "Territory Electric Track Pieces",
metric: RealOrdinaryConditionMetric::Territory(RuntimeTerritoryMetric::TrackPiecesElectric), kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Territory(
RuntimeTerritoryMetric::TrackPiecesElectric,
)),
}, },
RealOrdinaryConditionMetadata { RealOrdinaryConditionMetadata {
raw_condition_id: 2318, raw_condition_id: 2318,
label: "Territory Non-Electric Track Pieces", label: "Territory Non-Electric Track Pieces",
metric: RealOrdinaryConditionMetric::Territory( kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Territory(
RuntimeTerritoryMetric::TrackPiecesNonElectric, RuntimeTerritoryMetric::TrackPiecesNonElectric,
), )),
}, },
RealOrdinaryConditionMetadata { RealOrdinaryConditionMetadata {
raw_condition_id: 2323, raw_condition_id: 2323,
label: "Company-Territory Track Pieces", label: "Company-Territory Track Pieces",
metric: RealOrdinaryConditionMetric::CompanyTerritory(RuntimeTrackMetric::Total), kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyTerritory(
RuntimeTrackMetric::Total,
)),
}, },
RealOrdinaryConditionMetadata { RealOrdinaryConditionMetadata {
raw_condition_id: 2324, raw_condition_id: 2324,
label: "Company-Territory Single Track Pieces", label: "Company-Territory Single Track Pieces",
metric: RealOrdinaryConditionMetric::CompanyTerritory(RuntimeTrackMetric::Single), kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyTerritory(
RuntimeTrackMetric::Single,
)),
}, },
RealOrdinaryConditionMetadata { RealOrdinaryConditionMetadata {
raw_condition_id: 2325, raw_condition_id: 2325,
label: "Company-Territory Double Track Pieces", label: "Company-Territory Double Track Pieces",
metric: RealOrdinaryConditionMetric::CompanyTerritory(RuntimeTrackMetric::Double), kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyTerritory(
RuntimeTrackMetric::Double,
)),
}, },
RealOrdinaryConditionMetadata { RealOrdinaryConditionMetadata {
raw_condition_id: 2326, raw_condition_id: 2326,
label: "Company-Territory Transition Track Pieces", label: "Company-Territory Transition Track Pieces",
metric: RealOrdinaryConditionMetric::CompanyTerritory(RuntimeTrackMetric::Transition), kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyTerritory(
RuntimeTrackMetric::Transition,
)),
}, },
RealOrdinaryConditionMetadata { RealOrdinaryConditionMetadata {
raw_condition_id: 2327, raw_condition_id: 2327,
label: "Company-Territory Electric Track Pieces", label: "Company-Territory Electric Track Pieces",
metric: RealOrdinaryConditionMetric::CompanyTerritory(RuntimeTrackMetric::Electric), kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyTerritory(
RuntimeTrackMetric::Electric,
)),
}, },
RealOrdinaryConditionMetadata { RealOrdinaryConditionMetadata {
raw_condition_id: 2328, raw_condition_id: 2328,
label: "Company-Territory Non-Electric Track Pieces", label: "Company-Territory Non-Electric Track Pieces",
metric: RealOrdinaryConditionMetric::CompanyTerritory(RuntimeTrackMetric::NonElectric), kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyTerritory(
RuntimeTrackMetric::NonElectric,
)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_CANDIDATE_AVAILABILITY_CONDITION_TEMPLATE_ID,
label: "%1 Avail.",
kind: RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::CandidateAvailability),
},
RealOrdinaryConditionMetadata {
raw_condition_id: 2350,
label: "Economic Status",
kind: RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::EconomicStatus),
}, },
]; ];
@ -633,6 +698,15 @@ const KNOWN_TAG_DEFINITIONS: [KnownTagDefinition; 4] = [
}, },
]; ];
fn known_special_condition_definition_for_label_id(
label_id: u32,
) -> Option<KnownSpecialConditionDefinition> {
KNOWN_SPECIAL_CONDITION_DEFINITIONS
.iter()
.copied()
.find(|definition| !definition.hidden && definition.label_id == label_id)
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpKnownTagHit { pub struct SmpKnownTagHit {
pub tag_id: u16, pub tag_id: u16,
@ -2286,17 +2360,22 @@ fn parse_real_condition_row_summary(
let flag_bytes = row_bytes let flag_bytes = row_bytes
.get(5..PACKED_EVENT_REAL_CONDITION_ROW_LEN)? .get(5..PACKED_EVENT_REAL_CONDITION_ROW_LEN)?
.to_vec(); .to_vec();
let candidate_name_display = candidate_name.clone();
let candidate_name_ref = candidate_name_display.as_deref();
let ordinary_metadata = real_ordinary_condition_metadata(raw_condition_id); let ordinary_metadata = real_ordinary_condition_metadata(raw_condition_id);
let comparator = ordinary_metadata let comparator = ordinary_metadata
.and_then(|_| decode_real_condition_comparator(subtype)) .and_then(|_| decode_real_condition_comparator(subtype))
.map(condition_comparator_label); .map(condition_comparator_label);
let metric = ordinary_metadata.map(|metadata| metadata.label.to_string()); let metric =
ordinary_metadata.map(|metadata| real_ordinary_condition_metric_label(metadata, candidate_name_ref));
let threshold = ordinary_metadata.and_then(|_| decode_real_condition_threshold(&flag_bytes)); let threshold = ordinary_metadata.and_then(|_| decode_real_condition_threshold(&flag_bytes));
let requires_candidate_name_binding = ordinary_metadata.is_some_and(|metadata| { let requires_candidate_name_binding = ordinary_metadata.is_some_and(|metadata| {
matches!( matches!(
metadata.metric, metadata.kind,
RealOrdinaryConditionKind::Numeric(
RealOrdinaryConditionMetric::Territory(_) RealOrdinaryConditionMetric::Territory(_)
| RealOrdinaryConditionMetric::CompanyTerritory(_) | RealOrdinaryConditionMetric::CompanyTerritory(_)
)
) && candidate_name.is_some() ) && candidate_name.is_some()
}); });
let mut notes = Vec::new(); let mut notes = Vec::new();
@ -2312,6 +2391,14 @@ fn parse_real_condition_row_summary(
.to_string(), .to_string(),
); );
} }
if ordinary_metadata.is_some_and(|metadata| {
matches!(
metadata.kind,
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::CandidateAvailability)
) && candidate_name.is_none()
}) {
notes.push("candidate-availability condition row is missing its candidate-name side string".to_string());
}
Some(SmpLoadedPackedEventConditionRowSummary { Some(SmpLoadedPackedEventConditionRowSummary {
row_index, row_index,
raw_condition_id, raw_condition_id,
@ -2320,13 +2407,16 @@ fn parse_real_condition_row_summary(
candidate_name, candidate_name,
comparator, comparator,
metric, metric,
semantic_family: ordinary_metadata.map(|_| "numeric_threshold".to_string()), semantic_family: ordinary_metadata
.map(|metadata| real_ordinary_condition_semantic_family(metadata).to_string()),
semantic_preview: ordinary_metadata.and_then(|metadata| { semantic_preview: ordinary_metadata.and_then(|metadata| {
threshold.map(|value| { threshold.map(|value| {
let comparator_text = decode_real_condition_comparator(subtype) let comparator_text = decode_real_condition_comparator(subtype)
.map(condition_comparator_symbol) .map(condition_comparator_symbol)
.unwrap_or("?"); .unwrap_or("?");
format!("Test {} {} {}", metadata.label, comparator_text, value) let metric_label =
real_ordinary_condition_metric_label(metadata, candidate_name_ref);
format!("Test {} {} {}", metric_label, comparator_text, value)
}) })
}), }),
requires_candidate_name_binding, requires_candidate_name_binding,
@ -2384,6 +2474,47 @@ fn real_ordinary_condition_metadata(
.iter() .iter()
.copied() .copied()
.find(|metadata| metadata.raw_condition_id == raw_condition_id) .find(|metadata| metadata.raw_condition_id == raw_condition_id)
.or_else(|| {
known_special_condition_definition_for_label_id(raw_condition_id as u32).map(
|definition| RealOrdinaryConditionMetadata {
raw_condition_id,
label: definition.label,
kind: RealOrdinaryConditionKind::WorldState(
RealWorldConditionKind::SpecialCondition {
label: definition.label,
},
),
},
)
})
}
fn real_ordinary_condition_metric_label(
metadata: RealOrdinaryConditionMetadata,
candidate_name: Option<&str>,
) -> String {
match metadata.kind {
RealOrdinaryConditionKind::Numeric(_) => metadata.label.to_string(),
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::SpecialCondition { label }) => {
format!("Special Condition: {label}")
}
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::CandidateAvailability) => {
match candidate_name {
Some(name) => format!("Candidate Availability: {name}"),
None => "Candidate Availability".to_string(),
}
}
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::EconomicStatus) => {
"Economic Status".to_string()
}
}
}
fn real_ordinary_condition_semantic_family(metadata: RealOrdinaryConditionMetadata) -> &'static str {
match metadata.kind {
RealOrdinaryConditionKind::Numeric(_) => "numeric_threshold",
RealOrdinaryConditionKind::WorldState(_) => "world_state_threshold",
}
} }
fn decode_real_condition_comparator(subtype: u8) -> Option<RuntimeConditionComparator> { fn decode_real_condition_comparator(subtype: u8) -> Option<RuntimeConditionComparator> {
@ -2531,8 +2662,8 @@ fn decode_real_condition_row(
let metadata = real_ordinary_condition_metadata(row.raw_condition_id)?; let metadata = real_ordinary_condition_metadata(row.raw_condition_id)?;
let comparator = decode_real_condition_comparator(row.subtype)?; let comparator = decode_real_condition_comparator(row.subtype)?;
let value = decode_real_condition_threshold(&row.flag_bytes)?; let value = decode_real_condition_threshold(&row.flag_bytes)?;
match metadata.metric { match metadata.kind {
RealOrdinaryConditionMetric::Company(metric) => { RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(metric)) => {
Some(RuntimeCondition::CompanyNumericThreshold { Some(RuntimeCondition::CompanyNumericThreshold {
target: RuntimeCompanyTarget::ConditionTrueCompany, target: RuntimeCompanyTarget::ConditionTrueCompany,
metric, metric,
@ -2540,15 +2671,18 @@ fn decode_real_condition_row(
value, value,
}) })
} }
RealOrdinaryConditionMetric::Territory(metric) => negative_sentinel_scope RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Territory(metric)) => {
negative_sentinel_scope
.filter(|scope| scope.territory_scope_selector_is_0x63) .filter(|scope| scope.territory_scope_selector_is_0x63)
.map(|_| RuntimeCondition::TerritoryNumericThreshold { .map(|_| RuntimeCondition::TerritoryNumericThreshold {
target: RuntimeTerritoryTarget::AllTerritories, target: RuntimeTerritoryTarget::AllTerritories,
metric, metric,
comparator, comparator,
value, value,
}), })
RealOrdinaryConditionMetric::CompanyTerritory(metric) => negative_sentinel_scope }
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyTerritory(metric)) => {
negative_sentinel_scope
.filter(|scope| scope.territory_scope_selector_is_0x63) .filter(|scope| scope.territory_scope_selector_is_0x63)
.map(|_| RuntimeCondition::CompanyTerritoryNumericThreshold { .map(|_| RuntimeCondition::CompanyTerritoryNumericThreshold {
target: RuntimeCompanyTarget::ConditionTrueCompany, target: RuntimeCompanyTarget::ConditionTrueCompany,
@ -2556,7 +2690,27 @@ fn decode_real_condition_row(
metric, metric,
comparator, comparator,
value, value,
})
}
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::SpecialCondition {
label,
}) => Some(RuntimeCondition::SpecialConditionThreshold {
label: label.to_string(),
comparator,
value,
}), }),
RealOrdinaryConditionKind::WorldState(
RealWorldConditionKind::CandidateAvailability,
) => row.candidate_name.as_ref().map(|name| {
RuntimeCondition::CandidateAvailabilityThreshold {
name: name.clone(),
comparator,
value,
}
}),
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::EconomicStatus) => {
Some(RuntimeCondition::EconomicStatusCodeThreshold { comparator, value })
}
} }
} }
@ -7925,6 +8079,17 @@ mod tests {
bytes bytes
} }
fn build_real_condition_row_with_threshold(
raw_condition_id: i32,
subtype: u8,
threshold: i32,
candidate_name: Option<&str>,
) -> Vec<u8> {
let mut bytes = build_real_condition_row(raw_condition_id, subtype, 0, candidate_name);
bytes[5..9].copy_from_slice(&threshold.to_le_bytes());
bytes
}
struct RealGroupedEffectRowSpec<'a> { struct RealGroupedEffectRowSpec<'a> {
descriptor_id: u32, descriptor_id: u32,
opcode: u8, opcode: u8,
@ -8460,6 +8625,180 @@ mod tests {
assert!(summary.records[0].executable_import_ready); assert!(summary.records[0].executable_import_ready);
} }
#[test]
fn decodes_real_special_condition_threshold_from_checked_in_world_condition_metadata() {
let condition_row = build_real_condition_row_with_threshold(3835, 0, 1, None);
let record_body = build_real_event_record(
[b"World", b"", b"", b"", b"", b""],
Some(RealCompactControlSpec {
mode_byte_0x7ef: 7,
primary_selector_0x7f0: 0,
grouped_mode_0x7f4: 2,
one_shot_header_0x7f5: 0,
modifier_flag_0x7f9: 0,
modifier_flag_0x7fa: 0,
grouped_target_scope_ordinals_0x7fb: [0, 0, 0, 0],
grouped_scope_checkboxes_0x7ff: [0, 0, 0, 0],
summary_toggle_0x800: 1,
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
}),
&[condition_row],
[&[], &[], &[], &[]],
);
let mut bytes = Vec::new();
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_METADATA_TAG.to_le_bytes());
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION.to_le_bytes());
let header_words = [1u32, 4, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
for word in header_words {
bytes.extend_from_slice(&word.to_le_bytes());
}
bytes.extend_from_slice(&[0x00, 0x00]);
bytes.extend_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd]);
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_RECORDS_TAG.to_le_bytes());
bytes.extend_from_slice(&record_body);
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_CLOSE_TAG.to_le_bytes());
let report = inspect_smp_bytes(&bytes);
let summary = report
.event_runtime_collection_summary
.as_ref()
.expect("event runtime collection summary should parse");
assert_eq!(
summary.records[0].standalone_condition_rows[0]
.metric
.as_deref(),
Some("Special Condition: Use Wartime Cargos")
);
assert_eq!(
summary.records[0].standalone_condition_rows[0]
.semantic_family
.as_deref(),
Some("world_state_threshold")
);
assert_eq!(
summary.records[0].decoded_conditions,
vec![RuntimeCondition::SpecialConditionThreshold {
label: "Use Wartime Cargos".to_string(),
comparator: RuntimeConditionComparator::Ge,
value: 1,
}]
);
}
#[test]
fn decodes_real_candidate_availability_threshold_from_checked_in_world_condition_metadata() {
let condition_row =
build_real_condition_row_with_threshold(REAL_CANDIDATE_AVAILABILITY_CONDITION_TEMPLATE_ID, 0, 2, Some("Mogul"));
let record_body = build_real_event_record(
[b"World", b"", b"", b"", b"", b""],
Some(RealCompactControlSpec {
mode_byte_0x7ef: 7,
primary_selector_0x7f0: 0,
grouped_mode_0x7f4: 2,
one_shot_header_0x7f5: 0,
modifier_flag_0x7f9: 0,
modifier_flag_0x7fa: 0,
grouped_target_scope_ordinals_0x7fb: [0, 0, 0, 0],
grouped_scope_checkboxes_0x7ff: [0, 0, 0, 0],
summary_toggle_0x800: 1,
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
}),
&[condition_row],
[&[], &[], &[], &[]],
);
let mut bytes = Vec::new();
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_METADATA_TAG.to_le_bytes());
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION.to_le_bytes());
let header_words = [1u32, 4, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
for word in header_words {
bytes.extend_from_slice(&word.to_le_bytes());
}
bytes.extend_from_slice(&[0x00, 0x00]);
bytes.extend_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd]);
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_RECORDS_TAG.to_le_bytes());
bytes.extend_from_slice(&record_body);
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_CLOSE_TAG.to_le_bytes());
let report = inspect_smp_bytes(&bytes);
let summary = report
.event_runtime_collection_summary
.as_ref()
.expect("event runtime collection summary should parse");
assert_eq!(
summary.records[0].standalone_condition_rows[0]
.metric
.as_deref(),
Some("Candidate Availability: Mogul")
);
assert_eq!(
summary.records[0].decoded_conditions,
vec![RuntimeCondition::CandidateAvailabilityThreshold {
name: "Mogul".to_string(),
comparator: RuntimeConditionComparator::Ge,
value: 2,
}]
);
}
#[test]
fn decodes_real_economic_status_threshold_from_checked_in_world_condition_metadata() {
let condition_row = build_real_condition_row_with_threshold(2350, 0, 4, None);
let record_body = build_real_event_record(
[b"World", b"", b"", b"", b"", b""],
Some(RealCompactControlSpec {
mode_byte_0x7ef: 7,
primary_selector_0x7f0: 0,
grouped_mode_0x7f4: 2,
one_shot_header_0x7f5: 0,
modifier_flag_0x7f9: 0,
modifier_flag_0x7fa: 0,
grouped_target_scope_ordinals_0x7fb: [0, 0, 0, 0],
grouped_scope_checkboxes_0x7ff: [0, 0, 0, 0],
summary_toggle_0x800: 1,
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
}),
&[condition_row],
[&[], &[], &[], &[]],
);
let mut bytes = Vec::new();
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_METADATA_TAG.to_le_bytes());
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION.to_le_bytes());
let header_words = [1u32, 4, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
for word in header_words {
bytes.extend_from_slice(&word.to_le_bytes());
}
bytes.extend_from_slice(&[0x00, 0x00]);
bytes.extend_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd]);
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_RECORDS_TAG.to_le_bytes());
bytes.extend_from_slice(&record_body);
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_CLOSE_TAG.to_le_bytes());
let report = inspect_smp_bytes(&bytes);
let summary = report
.event_runtime_collection_summary
.as_ref()
.expect("event runtime collection summary should parse");
assert_eq!(
summary.records[0].standalone_condition_rows[0]
.metric
.as_deref(),
Some("Economic Status")
);
assert_eq!(
summary.records[0].decoded_conditions,
vec![RuntimeCondition::EconomicStatusCodeThreshold {
comparator: RuntimeConditionComparator::Ge,
value: 4,
}]
);
}
#[test] #[test]
fn keeps_real_world_flag_descriptor_parity_only_with_checked_in_metadata() { fn keeps_real_world_flag_descriptor_parity_only_with_checked_in_metadata() {
let grouped_row = build_real_grouped_effect_row(RealGroupedEffectRowSpec { let grouped_row = build_real_grouped_effect_row(RealGroupedEffectRowSpec {

View file

@ -101,6 +101,9 @@ The highest-value next passes are now:
candidate-availability thresholds, and economic-status-code thresholds now gate imported runtime candidate-availability thresholds, and economic-status-code thresholds now gate imported runtime
records, and the packed-event frontier now reports explicit unmapped world-condition and records, and the packed-event frontier now reports explicit unmapped world-condition and
world-descriptor buckets world-descriptor buckets
- that whole-game condition batch is now metadata-driven too: special-condition label ids,
economic-status, and the generic `%1 Avail.` candidate-availability template plus candidate-name
side strings all decode through checked-in world-condition metadata instead of fixture-only ids
- the first real whole-game grouped-descriptor batch is now metadata-driven too: checked-in - the first real whole-game grouped-descriptor batch is now metadata-driven too: checked-in
descriptor metadata covers special-condition and candidate-availability setters, while the descriptor metadata covers special-condition and candidate-availability setters, while the
current world-flag family stays parity-only until keyed flag identity is grounded well enough current world-flag family stays parity-only until keyed flag identity is grounded well enough

View file

@ -52,6 +52,11 @@ Implemented today:
through the same service path, and whole-game parity frontiers now report explicit unmapped through the same service path, and whole-game parity frontiers now report explicit unmapped
world-condition and world-descriptor buckets rather than falling back to the generic ordinary world-condition and world-descriptor buckets rather than falling back to the generic ordinary
or descriptor counts or descriptor counts
- checked-in whole-game condition metadata now drives that same world-side condition batch too:
special-condition thresholds use the real special-condition label ids, economic-status thresholds
use the checked-in world label id, and candidate-availability thresholds now decode through the
recovered `%1 Avail.` template plus the packed candidate-name side string instead of fixture-only
placeholder ids
- checked-in whole-game grouped-descriptor metadata now drives the first real world-side effect - checked-in whole-game grouped-descriptor metadata now drives the first real world-side effect
batch too: real special-condition and candidate-availability setter rows now decode and import batch too: real special-condition and candidate-availability setter rows now decode and import
through the ordinary runtime path, while world-flag rows remain parity-only until keyed flag through the ordinary runtime path, while world-flag rows remain parity-only until keyed flag
@ -59,10 +64,10 @@ Implemented today:
That means the next implementation work is breadth, not bootstrap. The recommended next slice is That means the next implementation work is breadth, not bootstrap. The recommended next slice is
broader real grouped-descriptor and ordinary condition-id coverage beyond the current access, broader real grouped-descriptor and ordinary condition-id coverage beyond the current access,
whole-game, train, player, and numeric-threshold batches, with the whole-game frontier now whole-game, train, player, and numeric-threshold batches, with the world-side frontier now
centered on still-unmapped world-flag families and any later state families that need stronger centered primarily on still-unmapped world-flag families and any later state families that need
checked-in descriptor or key recovery. Richer runtime ownership should still be added only where a stronger checked-in descriptor or key recovery. Richer runtime ownership should still be added only
later descriptor or condition family needs more than the current event-owned roster. where a later descriptor or condition family needs more than the current event-owned roster.
## Why This Boundary ## Why This Boundary

View file

@ -8,7 +8,7 @@
"notes": [ "notes": [
"tracked as JSON save-slice document rather than raw .smp", "tracked as JSON save-slice document rather than raw .smp",
"proves whole-game ordinary conditions gate imported runtime effects", "proves whole-game ordinary conditions gate imported runtime effects",
"whole-game grouped descriptor ids now line up with the checked-in metadata table" "whole-game condition ids now line up with the checked-in metadata tables instead of fixture-only placeholders"
] ]
}, },
"save_slice": { "save_slice": {
@ -187,7 +187,7 @@
"standalone_condition_rows": [ "standalone_condition_rows": [
{ {
"row_index": 0, "row_index": 0,
"raw_condition_id": 3901, "raw_condition_id": 3835,
"subtype": 0, "subtype": 0,
"flag_bytes": [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "flag_bytes": [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"candidate_name": null, "candidate_name": null,
@ -197,27 +197,27 @@
"semantic_preview": "Test Use Wartime Cargos >= 1", "semantic_preview": "Test Use Wartime Cargos >= 1",
"requires_candidate_name_binding": false, "requires_candidate_name_binding": false,
"notes": [ "notes": [
"tracked whole-game condition sample" "checked-in whole-game condition metadata sample"
] ]
}, },
{ {
"row_index": 1, "row_index": 1,
"raw_condition_id": 3902, "raw_condition_id": 435,
"subtype": 0, "subtype": 0,
"flag_bytes": [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "flag_bytes": [2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"candidate_name": null, "candidate_name": "Mogul",
"comparator": "ge", "comparator": "ge",
"metric": "Candidate Availability: Mogul", "metric": "Candidate Availability: Mogul",
"semantic_family": "world_state_threshold", "semantic_family": "world_state_threshold",
"semantic_preview": "Test Mogul >= 2", "semantic_preview": "Test Mogul >= 2",
"requires_candidate_name_binding": false, "requires_candidate_name_binding": false,
"notes": [ "notes": [
"tracked whole-game condition sample" "checked-in whole-game condition metadata sample with candidate-name side string"
] ]
}, },
{ {
"row_index": 2, "row_index": 2,
"raw_condition_id": 3903, "raw_condition_id": 2350,
"subtype": 0, "subtype": 0,
"flag_bytes": [4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "flag_bytes": [4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"candidate_name": null, "candidate_name": null,
@ -227,7 +227,7 @@
"semantic_preview": "Test Economic Status >= 4", "semantic_preview": "Test Economic Status >= 4",
"requires_candidate_name_binding": false, "requires_candidate_name_binding": false,
"notes": [ "notes": [
"tracked whole-game condition sample" "checked-in whole-game condition metadata sample"
] ]
} }
], ],

View file

@ -14,21 +14,18 @@
], ],
"expected_summary": { "expected_summary": {
"packed_event_collection_present": true, "packed_event_collection_present": true,
"packed_event_record_count": 2, "packed_event_record_count": 1,
"packed_event_decoded_record_count": 2, "packed_event_decoded_record_count": 1,
"packed_event_imported_runtime_record_count": 0, "packed_event_imported_runtime_record_count": 0,
"event_runtime_record_count": 0, "event_runtime_record_count": 0,
"packed_event_blocked_unmapped_world_descriptor_count": 1, "packed_event_blocked_unmapped_world_descriptor_count": 1,
"packed_event_blocked_unmapped_world_condition_count": 1 "packed_event_blocked_unmapped_world_condition_count": 0
}, },
"expected_state_fragment": { "expected_state_fragment": {
"packed_event_collection": { "packed_event_collection": {
"records": [ "records": [
{ {
"import_outcome": "blocked_unmapped_world_descriptor" "import_outcome": "blocked_unmapped_world_descriptor"
},
{
"import_outcome": "blocked_unmapped_world_condition"
} }
] ]
} }

View file

@ -2,12 +2,12 @@
"format_version": 1, "format_version": 1,
"save_slice_id": "packed-event-world-parity-save-slice", "save_slice_id": "packed-event-world-parity-save-slice",
"source": { "source": {
"description": "Tracked save-slice document preserving the current whole-game descriptor and condition frontier.", "description": "Tracked save-slice document preserving the remaining whole-game descriptor frontier.",
"original_save_filename": "captured-world-parity.gms", "original_save_filename": "captured-world-parity.gms",
"original_save_sha256": "world-parity-sample-sha256", "original_save_sha256": "world-parity-sample-sha256",
"notes": [ "notes": [
"tracked as JSON save-slice document rather than raw .smp", "tracked as JSON save-slice document rather than raw .smp",
"keeps one unmapped world descriptor and one unmapped world condition explicit", "keeps the still-unmapped world descriptor family explicit after the first real whole-game condition batch moved to checked-in metadata",
"whole-game world-flag descriptor identity is checked in, but keyed runtime mapping remains parity-only" "whole-game world-flag descriptor identity is checked in, but keyed runtime mapping remains parity-only"
] ]
}, },
@ -31,10 +31,10 @@
"close_tag_offset": 33024, "close_tag_offset": 33024,
"packed_state_version": 1001, "packed_state_version": 1001,
"packed_state_version_hex": "0x000003e9", "packed_state_version_hex": "0x000003e9",
"live_id_bound": 52, "live_id_bound": 49,
"live_record_count": 2, "live_record_count": 1,
"live_entry_ids": [49, 52], "live_entry_ids": [49],
"decoded_record_count": 2, "decoded_record_count": 1,
"imported_runtime_record_count": 0, "imported_runtime_record_count": 0,
"records": [ "records": [
{ {
@ -94,56 +94,6 @@
"notes": [ "notes": [
"world-side descriptor remains parity-only" "world-side descriptor remains parity-only"
] ]
},
{
"record_index": 1,
"live_entry_id": 52,
"payload_offset": 32464,
"payload_len": 120,
"decode_status": "parity_only",
"payload_family": "real_packed_v1",
"trigger_kind": 7,
"one_shot": false,
"compact_control": {
"mode_byte_0x7ef": 7,
"primary_selector_0x7f0": 0,
"grouped_mode_0x7f4": 2,
"one_shot_header_0x7f5": 0,
"modifier_flag_0x7f9": 0,
"modifier_flag_0x7fa": 0,
"grouped_target_scope_ordinals_0x7fb": [0, 0, 0, 0],
"grouped_scope_checkboxes_0x7ff": [0, 0, 0, 0],
"summary_toggle_0x800": 1,
"grouped_territory_selectors_0x80f": [-1, -1, -1, -1]
},
"text_bands": [],
"standalone_condition_row_count": 1,
"standalone_condition_rows": [
{
"row_index": 0,
"raw_condition_id": 3904,
"subtype": 0,
"flag_bytes": [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"candidate_name": null,
"comparator": "ge",
"metric": "Special Condition: Disable Stock Buying and Selling",
"semantic_family": "world_state_threshold",
"semantic_preview": "Test Disable Stock Buying and Selling >= 1",
"requires_candidate_name_binding": false,
"notes": [
"recovered world-side ordinary condition family without a checked-in executable mapping yet"
]
}
],
"negative_sentinel_scope": null,
"grouped_effect_row_counts": [0, 0, 0, 0],
"grouped_effect_rows": [],
"decoded_conditions": [],
"decoded_actions": [],
"executable_import_ready": false,
"notes": [
"world-side ordinary condition remains parity-only"
]
} }
] ]
}, },