Recover whole-game packed event conditions
This commit is contained in:
parent
a3f9a73766
commit
5d779263d2
7 changed files with 414 additions and 117 deletions
|
|
@ -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
|
||||
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
|
||||
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:
|
||||
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
|
||||
|
|
|
|||
|
|
@ -214,127 +214,192 @@ enum RealOrdinaryConditionMetric {
|
|||
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)]
|
||||
struct RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: i32,
|
||||
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 {
|
||||
raw_condition_id: 1802,
|
||||
label: "Current Cash",
|
||||
metric: RealOrdinaryConditionMetric::Company(RuntimeCompanyMetric::CurrentCash),
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
|
||||
RuntimeCompanyMetric::CurrentCash,
|
||||
)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: 951,
|
||||
label: "Total Debt",
|
||||
metric: RealOrdinaryConditionMetric::Company(RuntimeCompanyMetric::TotalDebt),
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
|
||||
RuntimeCompanyMetric::TotalDebt,
|
||||
)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: 2366,
|
||||
label: "Credit Rating",
|
||||
metric: RealOrdinaryConditionMetric::Company(RuntimeCompanyMetric::CreditRating),
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
|
||||
RuntimeCompanyMetric::CreditRating,
|
||||
)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: 2368,
|
||||
label: "Prime Rate",
|
||||
metric: RealOrdinaryConditionMetric::Company(RuntimeCompanyMetric::PrimeRate),
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
|
||||
RuntimeCompanyMetric::PrimeRate,
|
||||
)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: 2293,
|
||||
label: "Company Track Pieces",
|
||||
metric: RealOrdinaryConditionMetric::Company(RuntimeCompanyMetric::TrackPiecesTotal),
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
|
||||
RuntimeCompanyMetric::TrackPiecesTotal,
|
||||
)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: 2294,
|
||||
label: "Company Single Track Pieces",
|
||||
metric: RealOrdinaryConditionMetric::Company(RuntimeCompanyMetric::TrackPiecesSingle),
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
|
||||
RuntimeCompanyMetric::TrackPiecesSingle,
|
||||
)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: 2295,
|
||||
label: "Company Double Track Pieces",
|
||||
metric: RealOrdinaryConditionMetric::Company(RuntimeCompanyMetric::TrackPiecesDouble),
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
|
||||
RuntimeCompanyMetric::TrackPiecesDouble,
|
||||
)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: 2296,
|
||||
label: "Company Transition Track Pieces",
|
||||
metric: RealOrdinaryConditionMetric::Company(RuntimeCompanyMetric::TrackPiecesTransition),
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
|
||||
RuntimeCompanyMetric::TrackPiecesTransition,
|
||||
)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: 2297,
|
||||
label: "Company Electric Track Pieces",
|
||||
metric: RealOrdinaryConditionMetric::Company(RuntimeCompanyMetric::TrackPiecesElectric),
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
|
||||
RuntimeCompanyMetric::TrackPiecesElectric,
|
||||
)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: 2298,
|
||||
label: "Company Non-Electric Track Pieces",
|
||||
metric: RealOrdinaryConditionMetric::Company(RuntimeCompanyMetric::TrackPiecesNonElectric),
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
|
||||
RuntimeCompanyMetric::TrackPiecesNonElectric,
|
||||
)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: 2313,
|
||||
label: "Territory Track Pieces",
|
||||
metric: RealOrdinaryConditionMetric::Territory(RuntimeTerritoryMetric::TrackPiecesTotal),
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Territory(
|
||||
RuntimeTerritoryMetric::TrackPiecesTotal,
|
||||
)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: 2314,
|
||||
label: "Territory Single Track Pieces",
|
||||
metric: RealOrdinaryConditionMetric::Territory(RuntimeTerritoryMetric::TrackPiecesSingle),
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Territory(
|
||||
RuntimeTerritoryMetric::TrackPiecesSingle,
|
||||
)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: 2315,
|
||||
label: "Territory Double Track Pieces",
|
||||
metric: RealOrdinaryConditionMetric::Territory(RuntimeTerritoryMetric::TrackPiecesDouble),
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Territory(
|
||||
RuntimeTerritoryMetric::TrackPiecesDouble,
|
||||
)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: 2316,
|
||||
label: "Territory Transition Track Pieces",
|
||||
metric: RealOrdinaryConditionMetric::Territory(
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Territory(
|
||||
RuntimeTerritoryMetric::TrackPiecesTransition,
|
||||
),
|
||||
)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: 2317,
|
||||
label: "Territory Electric Track Pieces",
|
||||
metric: RealOrdinaryConditionMetric::Territory(RuntimeTerritoryMetric::TrackPiecesElectric),
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Territory(
|
||||
RuntimeTerritoryMetric::TrackPiecesElectric,
|
||||
)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: 2318,
|
||||
label: "Territory Non-Electric Track Pieces",
|
||||
metric: RealOrdinaryConditionMetric::Territory(
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Territory(
|
||||
RuntimeTerritoryMetric::TrackPiecesNonElectric,
|
||||
),
|
||||
)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: 2323,
|
||||
label: "Company-Territory Track Pieces",
|
||||
metric: RealOrdinaryConditionMetric::CompanyTerritory(RuntimeTrackMetric::Total),
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyTerritory(
|
||||
RuntimeTrackMetric::Total,
|
||||
)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: 2324,
|
||||
label: "Company-Territory Single Track Pieces",
|
||||
metric: RealOrdinaryConditionMetric::CompanyTerritory(RuntimeTrackMetric::Single),
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyTerritory(
|
||||
RuntimeTrackMetric::Single,
|
||||
)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: 2325,
|
||||
label: "Company-Territory Double Track Pieces",
|
||||
metric: RealOrdinaryConditionMetric::CompanyTerritory(RuntimeTrackMetric::Double),
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyTerritory(
|
||||
RuntimeTrackMetric::Double,
|
||||
)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: 2326,
|
||||
label: "Company-Territory Transition Track Pieces",
|
||||
metric: RealOrdinaryConditionMetric::CompanyTerritory(RuntimeTrackMetric::Transition),
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyTerritory(
|
||||
RuntimeTrackMetric::Transition,
|
||||
)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: 2327,
|
||||
label: "Company-Territory Electric Track Pieces",
|
||||
metric: RealOrdinaryConditionMetric::CompanyTerritory(RuntimeTrackMetric::Electric),
|
||||
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyTerritory(
|
||||
RuntimeTrackMetric::Electric,
|
||||
)),
|
||||
},
|
||||
RealOrdinaryConditionMetadata {
|
||||
raw_condition_id: 2328,
|
||||
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)]
|
||||
pub struct SmpKnownTagHit {
|
||||
pub tag_id: u16,
|
||||
|
|
@ -2286,17 +2360,22 @@ fn parse_real_condition_row_summary(
|
|||
let flag_bytes = row_bytes
|
||||
.get(5..PACKED_EVENT_REAL_CONDITION_ROW_LEN)?
|
||||
.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 comparator = ordinary_metadata
|
||||
.and_then(|_| decode_real_condition_comparator(subtype))
|
||||
.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 requires_candidate_name_binding = ordinary_metadata.is_some_and(|metadata| {
|
||||
matches!(
|
||||
metadata.metric,
|
||||
RealOrdinaryConditionMetric::Territory(_)
|
||||
| RealOrdinaryConditionMetric::CompanyTerritory(_)
|
||||
metadata.kind,
|
||||
RealOrdinaryConditionKind::Numeric(
|
||||
RealOrdinaryConditionMetric::Territory(_)
|
||||
| RealOrdinaryConditionMetric::CompanyTerritory(_)
|
||||
)
|
||||
) && candidate_name.is_some()
|
||||
});
|
||||
let mut notes = Vec::new();
|
||||
|
|
@ -2312,6 +2391,14 @@ fn parse_real_condition_row_summary(
|
|||
.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 {
|
||||
row_index,
|
||||
raw_condition_id,
|
||||
|
|
@ -2320,13 +2407,16 @@ fn parse_real_condition_row_summary(
|
|||
candidate_name,
|
||||
comparator,
|
||||
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| {
|
||||
threshold.map(|value| {
|
||||
let comparator_text = decode_real_condition_comparator(subtype)
|
||||
.map(condition_comparator_symbol)
|
||||
.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,
|
||||
|
|
@ -2384,6 +2474,47 @@ fn real_ordinary_condition_metadata(
|
|||
.iter()
|
||||
.copied()
|
||||
.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> {
|
||||
|
|
@ -2531,8 +2662,8 @@ fn decode_real_condition_row(
|
|||
let metadata = real_ordinary_condition_metadata(row.raw_condition_id)?;
|
||||
let comparator = decode_real_condition_comparator(row.subtype)?;
|
||||
let value = decode_real_condition_threshold(&row.flag_bytes)?;
|
||||
match metadata.metric {
|
||||
RealOrdinaryConditionMetric::Company(metric) => {
|
||||
match metadata.kind {
|
||||
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(metric)) => {
|
||||
Some(RuntimeCondition::CompanyNumericThreshold {
|
||||
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||
metric,
|
||||
|
|
@ -2540,15 +2671,18 @@ fn decode_real_condition_row(
|
|||
value,
|
||||
})
|
||||
}
|
||||
RealOrdinaryConditionMetric::Territory(metric) => negative_sentinel_scope
|
||||
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Territory(metric)) => {
|
||||
negative_sentinel_scope
|
||||
.filter(|scope| scope.territory_scope_selector_is_0x63)
|
||||
.map(|_| RuntimeCondition::TerritoryNumericThreshold {
|
||||
target: RuntimeTerritoryTarget::AllTerritories,
|
||||
metric,
|
||||
comparator,
|
||||
value,
|
||||
}),
|
||||
RealOrdinaryConditionMetric::CompanyTerritory(metric) => negative_sentinel_scope
|
||||
})
|
||||
}
|
||||
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyTerritory(metric)) => {
|
||||
negative_sentinel_scope
|
||||
.filter(|scope| scope.territory_scope_selector_is_0x63)
|
||||
.map(|_| RuntimeCondition::CompanyTerritoryNumericThreshold {
|
||||
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
||||
|
|
@ -2556,7 +2690,27 @@ fn decode_real_condition_row(
|
|||
metric,
|
||||
comparator,
|
||||
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
|
||||
}
|
||||
|
||||
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> {
|
||||
descriptor_id: u32,
|
||||
opcode: u8,
|
||||
|
|
@ -8460,6 +8625,180 @@ mod tests {
|
|||
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]
|
||||
fn keeps_real_world_flag_descriptor_parity_only_with_checked_in_metadata() {
|
||||
let grouped_row = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
|
||||
|
|
|
|||
|
|
@ -101,6 +101,9 @@ The highest-value next passes are now:
|
|||
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
|
||||
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
|
||||
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
|
||||
|
|
|
|||
|
|
@ -52,6 +52,11 @@ Implemented today:
|
|||
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
|
||||
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
|
||||
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
|
||||
|
|
@ -59,10 +64,10 @@ Implemented today:
|
|||
|
||||
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,
|
||||
whole-game, train, player, and numeric-threshold batches, with the whole-game frontier now
|
||||
centered on still-unmapped world-flag families and any later state families that need stronger
|
||||
checked-in descriptor or key recovery. Richer runtime ownership should still be added only where a
|
||||
later descriptor or condition family needs more than the current event-owned roster.
|
||||
whole-game, train, player, and numeric-threshold batches, with the world-side frontier now
|
||||
centered primarily on still-unmapped world-flag families and any later state families that need
|
||||
stronger checked-in descriptor or key recovery. Richer runtime ownership should still be added only
|
||||
where a later descriptor or condition family needs more than the current event-owned roster.
|
||||
|
||||
## Why This Boundary
|
||||
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@
|
|||
"description": "Tracked save-slice document with whole-game conditions gating a whole-game effect.",
|
||||
"original_save_filename": "captured-world-condition-gated.gms",
|
||||
"original_save_sha256": "world-condition-gated-sample-sha256",
|
||||
"notes": [
|
||||
"tracked as JSON save-slice document rather than raw .smp",
|
||||
"proves whole-game ordinary conditions gate imported runtime effects",
|
||||
"whole-game grouped descriptor ids now line up with the checked-in metadata table"
|
||||
]
|
||||
"notes": [
|
||||
"tracked as JSON save-slice document rather than raw .smp",
|
||||
"proves whole-game ordinary conditions gate imported runtime effects",
|
||||
"whole-game condition ids now line up with the checked-in metadata tables instead of fixture-only placeholders"
|
||||
]
|
||||
},
|
||||
"save_slice": {
|
||||
"file_extension_hint": "gms",
|
||||
|
|
@ -187,7 +187,7 @@
|
|||
"standalone_condition_rows": [
|
||||
{
|
||||
"row_index": 0,
|
||||
"raw_condition_id": 3901,
|
||||
"raw_condition_id": 3835,
|
||||
"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,
|
||||
|
|
@ -197,27 +197,27 @@
|
|||
"semantic_preview": "Test Use Wartime Cargos >= 1",
|
||||
"requires_candidate_name_binding": false,
|
||||
"notes": [
|
||||
"tracked whole-game condition sample"
|
||||
"checked-in whole-game condition metadata sample"
|
||||
]
|
||||
},
|
||||
{
|
||||
"row_index": 1,
|
||||
"raw_condition_id": 3902,
|
||||
"raw_condition_id": 435,
|
||||
"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],
|
||||
"candidate_name": null,
|
||||
"candidate_name": "Mogul",
|
||||
"comparator": "ge",
|
||||
"metric": "Candidate Availability: Mogul",
|
||||
"semantic_family": "world_state_threshold",
|
||||
"semantic_preview": "Test Mogul >= 2",
|
||||
"requires_candidate_name_binding": false,
|
||||
"notes": [
|
||||
"tracked whole-game condition sample"
|
||||
"checked-in whole-game condition metadata sample with candidate-name side string"
|
||||
]
|
||||
},
|
||||
{
|
||||
"row_index": 2,
|
||||
"raw_condition_id": 3903,
|
||||
"raw_condition_id": 2350,
|
||||
"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],
|
||||
"candidate_name": null,
|
||||
|
|
@ -227,7 +227,7 @@
|
|||
"semantic_preview": "Test Economic Status >= 4",
|
||||
"requires_candidate_name_binding": false,
|
||||
"notes": [
|
||||
"tracked whole-game condition sample"
|
||||
"checked-in whole-game condition metadata sample"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -14,21 +14,18 @@
|
|||
],
|
||||
"expected_summary": {
|
||||
"packed_event_collection_present": true,
|
||||
"packed_event_record_count": 2,
|
||||
"packed_event_decoded_record_count": 2,
|
||||
"packed_event_record_count": 1,
|
||||
"packed_event_decoded_record_count": 1,
|
||||
"packed_event_imported_runtime_record_count": 0,
|
||||
"event_runtime_record_count": 0,
|
||||
"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": {
|
||||
"packed_event_collection": {
|
||||
"records": [
|
||||
{
|
||||
"import_outcome": "blocked_unmapped_world_descriptor"
|
||||
},
|
||||
{
|
||||
"import_outcome": "blocked_unmapped_world_condition"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@
|
|||
"format_version": 1,
|
||||
"save_slice_id": "packed-event-world-parity-save-slice",
|
||||
"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_sha256": "world-parity-sample-sha256",
|
||||
"notes": [
|
||||
"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"
|
||||
]
|
||||
},
|
||||
|
|
@ -31,10 +31,10 @@
|
|||
"close_tag_offset": 33024,
|
||||
"packed_state_version": 1001,
|
||||
"packed_state_version_hex": "0x000003e9",
|
||||
"live_id_bound": 52,
|
||||
"live_record_count": 2,
|
||||
"live_entry_ids": [49, 52],
|
||||
"decoded_record_count": 2,
|
||||
"live_id_bound": 49,
|
||||
"live_record_count": 1,
|
||||
"live_entry_ids": [49],
|
||||
"decoded_record_count": 1,
|
||||
"imported_runtime_record_count": 0,
|
||||
"records": [
|
||||
{
|
||||
|
|
@ -94,56 +94,6 @@
|
|||
"notes": [
|
||||
"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"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue