Carry company stat-band roots into runtime state
This commit is contained in:
parent
8473471a79
commit
1e0f88bd62
9 changed files with 180 additions and 15 deletions
|
|
@ -51,8 +51,9 @@ math. The next shared company-side slice is now rehosted too: save-native compan
|
|||
flow into a typed company market/cache map on runtime service state, carrying outstanding shares,
|
||||
saved support/share-price/cache words, chairman salary lanes, calendar words, and connection
|
||||
latches for each live company. That map now appears in runtime summaries and save-slice exports,
|
||||
so later company stat-family / finance readers can build on owned state instead of another round
|
||||
of single-field save-offset guesses. A checked-in
|
||||
and it now also carries the first grounded stat-band root windows at `[company+0x0cfb]`,
|
||||
`[company+0x0d7f]`, and `[company+0x1c47]`, so later company stat-family / finance readers can
|
||||
build on owned state instead of another round of single-field save-offset guesses. A checked-in
|
||||
The working rule on the remaining frontier is explicit now too: when a lane is still ambiguous, we
|
||||
should prefer rehosting the owning source state or the real reader/setter family rather than
|
||||
guessing one more derived leaf field from nearby offsets. A checked-in
|
||||
|
|
|
|||
|
|
@ -96,6 +96,12 @@ pub struct ExpectedRuntimeSummary {
|
|||
#[serde(default)]
|
||||
pub selected_company_mutable_support_scalar_value_f32_text: Option<String>,
|
||||
#[serde(default)]
|
||||
pub selected_company_stat_band_root_0cfb_count: Option<usize>,
|
||||
#[serde(default)]
|
||||
pub selected_company_stat_band_root_0d7f_count: Option<usize>,
|
||||
#[serde(default)]
|
||||
pub selected_company_stat_band_root_1c47_count: Option<usize>,
|
||||
#[serde(default)]
|
||||
pub player_count: Option<usize>,
|
||||
#[serde(default)]
|
||||
pub chairman_profile_count: Option<usize>,
|
||||
|
|
@ -609,6 +615,30 @@ impl ExpectedRuntimeSummary {
|
|||
));
|
||||
}
|
||||
}
|
||||
if let Some(count) = self.selected_company_stat_band_root_0cfb_count {
|
||||
if actual.selected_company_stat_band_root_0cfb_count != count {
|
||||
mismatches.push(format!(
|
||||
"selected_company_stat_band_root_0cfb_count mismatch: expected {count}, got {}",
|
||||
actual.selected_company_stat_band_root_0cfb_count
|
||||
));
|
||||
}
|
||||
}
|
||||
if let Some(count) = self.selected_company_stat_band_root_0d7f_count {
|
||||
if actual.selected_company_stat_band_root_0d7f_count != count {
|
||||
mismatches.push(format!(
|
||||
"selected_company_stat_band_root_0d7f_count mismatch: expected {count}, got {}",
|
||||
actual.selected_company_stat_band_root_0d7f_count
|
||||
));
|
||||
}
|
||||
}
|
||||
if let Some(count) = self.selected_company_stat_band_root_1c47_count {
|
||||
if actual.selected_company_stat_band_root_1c47_count != count {
|
||||
mismatches.push(format!(
|
||||
"selected_company_stat_band_root_1c47_count mismatch: expected {count}, got {}",
|
||||
actual.selected_company_stat_band_root_1c47_count
|
||||
));
|
||||
}
|
||||
}
|
||||
if let Some(count) = self.player_count {
|
||||
if actual.player_count != count {
|
||||
mismatches.push(format!(
|
||||
|
|
|
|||
|
|
@ -5057,6 +5057,9 @@ mod tests {
|
|||
prior_issue_calendar_word: 4,
|
||||
city_connection_latch: true,
|
||||
linked_transit_latch: false,
|
||||
stat_band_root_0cfb_candidates: Vec::new(),
|
||||
stat_band_root_0d7f_candidates: Vec::new(),
|
||||
stat_band_root_1c47_candidates: Vec::new(),
|
||||
}),
|
||||
},
|
||||
crate::SmpLoadedCompanyRosterEntry {
|
||||
|
|
@ -5100,6 +5103,9 @@ mod tests {
|
|||
prior_issue_calendar_word: 2,
|
||||
city_connection_latch: false,
|
||||
linked_transit_latch: true,
|
||||
stat_band_root_0cfb_candidates: Vec::new(),
|
||||
stat_band_root_0d7f_candidates: Vec::new(),
|
||||
stat_band_root_1c47_candidates: Vec::new(),
|
||||
}),
|
||||
},
|
||||
],
|
||||
|
|
@ -6386,6 +6392,9 @@ mod tests {
|
|||
prior_issue_calendar_word: 3,
|
||||
city_connection_latch: false,
|
||||
linked_transit_latch: true,
|
||||
stat_band_root_0cfb_candidates: Vec::new(),
|
||||
stat_band_root_0d7f_candidates: Vec::new(),
|
||||
stat_band_root_1c47_candidates: Vec::new(),
|
||||
},
|
||||
)]),
|
||||
..RuntimeServiceState::default()
|
||||
|
|
|
|||
|
|
@ -48,16 +48,17 @@ pub use runtime::{
|
|||
RuntimeCargoProductionTarget, RuntimeChairmanMetric, RuntimeChairmanProfile,
|
||||
RuntimeChairmanTarget, RuntimeCompany, RuntimeCompanyConditionTestScope,
|
||||
RuntimeCompanyControllerKind, RuntimeCompanyMarketState, RuntimeCompanyMetric,
|
||||
RuntimeCompanyTarget, RuntimeCompanyTerritoryAccess, RuntimeCompanyTerritoryTrackPieceCount,
|
||||
RuntimeCondition, RuntimeConditionComparator, RuntimeEffect, RuntimeEventRecord,
|
||||
RuntimeEventRecordTemplate, RuntimeLocomotiveCatalogEntry, RuntimePackedEventCollectionSummary,
|
||||
RuntimePackedEventCompactControlSummary, RuntimePackedEventConditionRowSummary,
|
||||
RuntimePackedEventGroupedEffectRowSummary, RuntimePackedEventNegativeSentinelScopeSummary,
|
||||
RuntimePackedEventRecordSummary, RuntimePackedEventTextBandSummary, RuntimePlayer,
|
||||
RuntimePlayerConditionTestScope, RuntimePlayerTarget, RuntimeSaveProfileState,
|
||||
RuntimeServiceState, RuntimeState, RuntimeTerritory, RuntimeTerritoryMetric,
|
||||
RuntimeTerritoryTarget, RuntimeTrackMetric, RuntimeTrackPieceCounts, RuntimeTrain,
|
||||
RuntimeWorldFinanceNeighborhoodCandidate, RuntimeWorldRestoreState,
|
||||
RuntimeCompanyStatBandCandidate, RuntimeCompanyTarget, RuntimeCompanyTerritoryAccess,
|
||||
RuntimeCompanyTerritoryTrackPieceCount, RuntimeCondition, RuntimeConditionComparator,
|
||||
RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate, RuntimeLocomotiveCatalogEntry,
|
||||
RuntimePackedEventCollectionSummary, RuntimePackedEventCompactControlSummary,
|
||||
RuntimePackedEventConditionRowSummary, RuntimePackedEventGroupedEffectRowSummary,
|
||||
RuntimePackedEventNegativeSentinelScopeSummary, RuntimePackedEventRecordSummary,
|
||||
RuntimePackedEventTextBandSummary, RuntimePlayer, RuntimePlayerConditionTestScope,
|
||||
RuntimePlayerTarget, RuntimeSaveProfileState, RuntimeServiceState, RuntimeState,
|
||||
RuntimeTerritory, RuntimeTerritoryMetric, RuntimeTerritoryTarget, RuntimeTrackMetric,
|
||||
RuntimeTrackPieceCounts, RuntimeTrain, RuntimeWorldFinanceNeighborhoodCandidate,
|
||||
RuntimeWorldRestoreState,
|
||||
};
|
||||
pub use smp::{
|
||||
SMP_FOUR_SIDECAR_BYTE_PLANES_MIN_BUNDLE_VERSION, SmpAlignedRuntimeRuleBandLane,
|
||||
|
|
|
|||
|
|
@ -84,6 +84,12 @@ pub struct RuntimeCompanyMarketState {
|
|||
pub city_connection_latch: bool,
|
||||
#[serde(default)]
|
||||
pub linked_transit_latch: bool,
|
||||
#[serde(default)]
|
||||
pub stat_band_root_0cfb_candidates: Vec<RuntimeCompanyStatBandCandidate>,
|
||||
#[serde(default)]
|
||||
pub stat_band_root_0d7f_candidates: Vec<RuntimeCompanyStatBandCandidate>,
|
||||
#[serde(default)]
|
||||
pub stat_band_root_1c47_candidates: Vec<RuntimeCompanyStatBandCandidate>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
|
|
@ -871,6 +877,17 @@ pub struct RuntimeWorldFinanceNeighborhoodCandidate {
|
|||
pub value_f32_text: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct RuntimeCompanyStatBandCandidate {
|
||||
pub label: String,
|
||||
pub relative_offset: usize,
|
||||
pub relative_offset_hex: String,
|
||||
pub raw_u32: u32,
|
||||
pub raw_u32_hex: String,
|
||||
pub value_i32: i32,
|
||||
pub value_f32_text: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
pub struct RuntimeWorldRestoreState {
|
||||
#[serde(default)]
|
||||
|
|
|
|||
|
|
@ -3754,6 +3754,27 @@ fn parse_save_company_roster_probe(
|
|||
bytes,
|
||||
record_offset + SAVE_COMPANY_RECORD_TAKEOVER_COOLDOWN_OFFSET,
|
||||
)?;
|
||||
let stat_band_root_0cfb_candidates =
|
||||
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0CFB_CANDIDATE_FIELDS
|
||||
.iter()
|
||||
.map(|(label, relative_offset)| {
|
||||
build_save_dword_candidate(bytes, record_offset, label, *relative_offset)
|
||||
})
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
let stat_band_root_0d7f_candidates =
|
||||
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0D7F_CANDIDATE_FIELDS
|
||||
.iter()
|
||||
.map(|(label, relative_offset)| {
|
||||
build_save_dword_candidate(bytes, record_offset, label, *relative_offset)
|
||||
})
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
let stat_band_root_1c47_candidates =
|
||||
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_1C47_CANDIDATE_FIELDS
|
||||
.iter()
|
||||
.map(|(label, relative_offset)| {
|
||||
build_save_dword_candidate(bytes, record_offset, label, *relative_offset)
|
||||
})
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
entries.push(SmpLoadedCompanyRosterEntry {
|
||||
company_id,
|
||||
active,
|
||||
|
|
@ -3788,6 +3809,18 @@ fn parse_save_company_roster_probe(
|
|||
prior_issue_calendar_word,
|
||||
city_connection_latch,
|
||||
linked_transit_latch,
|
||||
stat_band_root_0cfb_candidates: stat_band_root_0cfb_candidates
|
||||
.iter()
|
||||
.map(runtime_company_stat_band_candidate_from_save)
|
||||
.collect(),
|
||||
stat_band_root_0d7f_candidates: stat_band_root_0d7f_candidates
|
||||
.iter()
|
||||
.map(runtime_company_stat_band_candidate_from_save)
|
||||
.collect(),
|
||||
stat_band_root_1c47_candidates: stat_band_root_1c47_candidates
|
||||
.iter()
|
||||
.map(runtime_company_stat_band_candidate_from_save)
|
||||
.collect(),
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
|
@ -3801,6 +3834,20 @@ fn parse_save_company_roster_probe(
|
|||
})
|
||||
}
|
||||
|
||||
fn runtime_company_stat_band_candidate_from_save(
|
||||
candidate: &SmpSaveDwordCandidate,
|
||||
) -> crate::RuntimeCompanyStatBandCandidate {
|
||||
crate::RuntimeCompanyStatBandCandidate {
|
||||
label: candidate.label.clone(),
|
||||
relative_offset: candidate.relative_offset,
|
||||
relative_offset_hex: candidate.relative_offset_hex.clone(),
|
||||
raw_u32: candidate.raw_u32,
|
||||
raw_u32_hex: candidate.raw_u32_hex.clone(),
|
||||
value_i32: candidate.value_i32,
|
||||
value_f32_text: format!("{:.6}", candidate.value_f32),
|
||||
}
|
||||
}
|
||||
|
||||
fn detect_save_company_record_start_offset(
|
||||
bytes: &[u8],
|
||||
header_probe: &SmpSaveTaggedCollectionHeaderProbe,
|
||||
|
|
|
|||
|
|
@ -49,6 +49,9 @@ pub struct RuntimeSummary {
|
|||
pub selected_company_outstanding_shares: Option<u32>,
|
||||
pub selected_company_cached_share_price_value_f32_text: Option<String>,
|
||||
pub selected_company_mutable_support_scalar_value_f32_text: Option<String>,
|
||||
pub selected_company_stat_band_root_0cfb_count: usize,
|
||||
pub selected_company_stat_band_root_0d7f_count: usize,
|
||||
pub selected_company_stat_band_root_1c47_count: usize,
|
||||
pub selected_company_last_dividend_year: Option<u32>,
|
||||
pub selected_company_chairman_bonus_year: Option<u32>,
|
||||
pub selected_company_chairman_bonus_amount: Option<i32>,
|
||||
|
|
@ -249,6 +252,15 @@ impl RuntimeSummary {
|
|||
.map(|market_state| {
|
||||
raw_u32_to_f32_text(market_state.mutable_support_scalar_raw_u32)
|
||||
}),
|
||||
selected_company_stat_band_root_0cfb_count: selected_company_market_state
|
||||
.map(|market_state| market_state.stat_band_root_0cfb_candidates.len())
|
||||
.unwrap_or(0),
|
||||
selected_company_stat_band_root_0d7f_count: selected_company_market_state
|
||||
.map(|market_state| market_state.stat_band_root_0d7f_candidates.len())
|
||||
.unwrap_or(0),
|
||||
selected_company_stat_band_root_1c47_count: selected_company_market_state
|
||||
.map(|market_state| market_state.stat_band_root_1c47_candidates.len())
|
||||
.unwrap_or(0),
|
||||
selected_company_last_dividend_year: selected_company_market_state
|
||||
.map(|market_state| market_state.last_dividend_year)
|
||||
.filter(|year| *year != 0),
|
||||
|
|
@ -1890,6 +1902,48 @@ mod tests {
|
|||
prior_issue_calendar_word: 4,
|
||||
city_connection_latch: true,
|
||||
linked_transit_latch: false,
|
||||
stat_band_root_0cfb_candidates: vec![
|
||||
crate::RuntimeCompanyStatBandCandidate {
|
||||
label: "stat_band_0cfb_word_1".to_string(),
|
||||
relative_offset: 0x0cfb,
|
||||
relative_offset_hex: "0xcfb".to_string(),
|
||||
raw_u32: 1,
|
||||
raw_u32_hex: "0x00000001".to_string(),
|
||||
value_i32: 1,
|
||||
value_f32_text: "0.000000".to_string(),
|
||||
},
|
||||
crate::RuntimeCompanyStatBandCandidate {
|
||||
label: "stat_band_0cfb_word_2".to_string(),
|
||||
relative_offset: 0x0cff,
|
||||
relative_offset_hex: "0xcff".to_string(),
|
||||
raw_u32: 2,
|
||||
raw_u32_hex: "0x00000002".to_string(),
|
||||
value_i32: 2,
|
||||
value_f32_text: "0.000000".to_string(),
|
||||
},
|
||||
],
|
||||
stat_band_root_0d7f_candidates: vec![
|
||||
crate::RuntimeCompanyStatBandCandidate {
|
||||
label: "stat_band_0d7f_word_1".to_string(),
|
||||
relative_offset: 0x0d7f,
|
||||
relative_offset_hex: "0xd7f".to_string(),
|
||||
raw_u32: 3,
|
||||
raw_u32_hex: "0x00000003".to_string(),
|
||||
value_i32: 3,
|
||||
value_f32_text: "0.000000".to_string(),
|
||||
},
|
||||
],
|
||||
stat_band_root_1c47_candidates: vec![
|
||||
crate::RuntimeCompanyStatBandCandidate {
|
||||
label: "stat_band_1c47_word_1".to_string(),
|
||||
relative_offset: 0x1c47,
|
||||
relative_offset_hex: "0x1c47".trim_start_matches("0x").to_string(),
|
||||
raw_u32: 4,
|
||||
raw_u32_hex: "0x00000004".to_string(),
|
||||
value_i32: 4,
|
||||
value_f32_text: "0.000000".to_string(),
|
||||
},
|
||||
],
|
||||
},
|
||||
)]),
|
||||
..RuntimeServiceState::default()
|
||||
|
|
@ -1907,6 +1961,9 @@ mod tests {
|
|||
summary.selected_company_mutable_support_scalar_value_f32_text,
|
||||
Some("1.000000".to_string())
|
||||
);
|
||||
assert_eq!(summary.selected_company_stat_band_root_0cfb_count, 2);
|
||||
assert_eq!(summary.selected_company_stat_band_root_0d7f_count, 1);
|
||||
assert_eq!(summary.selected_company_stat_band_root_1c47_count, 1);
|
||||
assert_eq!(summary.selected_company_last_dividend_year, Some(1841));
|
||||
assert_eq!(summary.selected_company_chairman_bonus_year, Some(1842));
|
||||
assert_eq!(summary.selected_company_chairman_bonus_amount, Some(750));
|
||||
|
|
|
|||
|
|
@ -112,8 +112,9 @@ The highest-value next passes are now:
|
|||
tuning band, derived holdings-at-share-price and cached purchasing-power totals,
|
||||
context, company dword candidate windows, and richer chairman qword cache views; the current
|
||||
rehosted company-side owner state now also includes a typed market/cache map carrying saved
|
||||
outstanding-shares, support/share-price/cache words, salary lanes, calendar words, and
|
||||
connection latches for each live company, so later finance/stat-family readers can attach to
|
||||
outstanding-shares, support/share-price/cache words, salary lanes, calendar words, connection
|
||||
latches, and the first grounded stat-band root windows at `[company+0x0cfb]`, `[company+0x0d7f]`,
|
||||
and `[company+0x1c47]` for each live company, so later finance/stat-family readers can attach to
|
||||
owned runtime data instead of one more guessed save offset
|
||||
- the project rule on the remaining closure work is now explicit too: when one runtime-facing field
|
||||
is still ambiguous, prefer rehosting the owning source state or real reader/setter family first
|
||||
|
|
|
|||
|
|
@ -197,7 +197,9 @@ later tranche once stronger evidence exists, but the current project rule is exp
|
|||
rehosting shared owner state and reader/setter families first, and only guess at one more leaf
|
||||
field when that richer owning-state path is blocked. Richer runtime ownership should still be added
|
||||
where later descriptor, stat-family, or simulation work needs more than the current event-owned
|
||||
roster.
|
||||
roster. The current owned company-side roster now includes not just the market/cache lanes but also
|
||||
the first grounded stat-band root windows at `[company+0x0cfb]`, `[company+0x0d7f]`, and
|
||||
`[company+0x1c47]`, so later finance readers can target saved owner state directly.
|
||||
|
||||
## Why This Boundary
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue