Rehost annual stock repurchase policy branch

This commit is contained in:
Jan Petykiewicz 2026-04-18 00:28:54 -07:00
commit 0658626a57
8 changed files with 592 additions and 17 deletions

View file

@ -95,6 +95,9 @@ headlessly as a pure runtime reader over owned annual-finance state, support-adj
and current world finance policy rather than as a notes-only atlas fragment. The later deep-
distress bankruptcy fallback is now rehosted on that same owner surface too, using the save-native
cash reader seam plus the first three trailing net-profit years instead of another ad hoc probe.
The same seam now also carries the fixed-world building-density growth setting plus the linked
chairman personality byte, which is enough to run the annual stock-repurchase gate as another
pure reader over owned save-native state instead of a guessed finance-side approximation.
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

View file

@ -108,6 +108,7 @@ struct SaveSliceProjection {
has_chairman_selection_override: bool,
selected_chairman_profile_id: Option<u32>,
chairman_issue_opinion_terms_raw_i32: BTreeMap<u32, Vec<i32>>,
chairman_personality_raw_u8: BTreeMap<u32, u8>,
candidate_availability: BTreeMap<String, u32>,
named_locomotive_availability: BTreeMap<String, u32>,
locomotive_catalog: Option<Vec<RuntimeLocomotiveCatalogEntry>>,
@ -318,6 +319,7 @@ pub fn project_save_slice_to_runtime_state_import(
.world_issue_opinion_base_terms_raw_i32,
company_market_state: projection.company_market_state,
chairman_issue_opinion_terms_raw_i32: projection.chairman_issue_opinion_terms_raw_i32,
chairman_personality_raw_u8: projection.chairman_personality_raw_u8,
..RuntimeServiceState::default()
},
};
@ -448,6 +450,11 @@ pub fn project_save_slice_overlay_to_runtime_state_import(
.chairman_issue_opinion_terms_raw_i32
.clone()
},
chairman_personality_raw_u8: if projection.has_chairman_projection {
projection.chairman_personality_raw_u8
} else {
base_state.service_state.chairman_personality_raw_u8.clone()
},
..base_state.service_state.clone()
},
};
@ -867,6 +874,10 @@ fn project_save_slice_components(
.world_finance_neighborhood_state
.as_ref()
.map(|state| state.dividend_policy_raw_u8),
building_density_growth_setting_raw_u32: save_slice
.world_finance_neighborhood_state
.as_ref()
.map(|state| state.building_density_growth_setting_raw_u32),
stock_issue_and_buyback_allowed: save_slice
.world_finance_neighborhood_state
.as_ref()
@ -1227,6 +1238,7 @@ fn project_save_slice_components(
has_chairman_selection_override,
selected_chairman_profile_id,
chairman_issue_opinion_terms_raw_i32,
chairman_personality_raw_u8,
) = if let Some(table) = &save_slice.chairman_profile_table {
metadata.insert(
"save_slice.chairman_profile_table_source_kind".to_string(),
@ -1253,6 +1265,7 @@ fn project_save_slice_components(
table.selected_chairman_profile_id.is_some(),
table.selected_chairman_profile_id,
BTreeMap::new(),
BTreeMap::new(),
)
} else {
(
@ -1279,10 +1292,26 @@ fn project_save_slice_components(
.iter()
.map(|entry| (entry.profile_id, entry.issue_opinion_terms_raw_i32.clone()))
.collect::<BTreeMap<_, _>>(),
table
.entries
.iter()
.filter_map(|entry| {
entry
.personality_byte_0x291
.map(|value| (entry.profile_id, value))
})
.collect::<BTreeMap<_, _>>(),
)
}
} else {
(Vec::new(), false, false, None, BTreeMap::new())
(
Vec::new(),
false,
false,
None,
BTreeMap::new(),
BTreeMap::new(),
)
};
let named_locomotive_cost = BTreeMap::new();
@ -1374,6 +1403,7 @@ fn project_save_slice_components(
has_chairman_selection_override,
selected_chairman_profile_id,
chairman_issue_opinion_terms_raw_i32,
chairman_personality_raw_u8,
candidate_availability,
named_locomotive_availability,
locomotive_catalog,
@ -5326,6 +5356,7 @@ mod tests {
holdings_value_total: 700,
net_worth_total: 1200,
purchasing_power_total: 1500,
personality_byte_0x291: Some(12),
issue_opinion_terms_raw_i32: Vec::new(),
},
crate::SmpLoadedChairmanProfileEntry {
@ -5338,6 +5369,7 @@ mod tests {
holdings_value_total: 600,
net_worth_total: 900,
purchasing_power_total: 1100,
personality_byte_0x291: Some(20),
issue_opinion_terms_raw_i32: Vec::new(),
},
],
@ -6019,6 +6051,8 @@ mod tests {
bankruptcy_policy_raw_hex: "0x00".to_string(),
dividend_policy_raw_u8: 1,
dividend_policy_raw_hex: "0x01".to_string(),
building_density_growth_setting_raw_u32: 1,
building_density_growth_setting_raw_hex: "0x00000001".to_string(),
labels: vec![
"current_calendar_tuple_word".to_string(),
"current_calendar_tuple_word_2".to_string(),
@ -13956,6 +13990,7 @@ mod tests {
world_issue_opinion_base_terms_raw_i32: Vec::new(),
company_market_state: BTreeMap::new(),
chairman_issue_opinion_terms_raw_i32: BTreeMap::new(),
chairman_personality_raw_u8: BTreeMap::new(),
},
};
let save_slice = SmpLoadedSaveSlice {

View file

@ -53,12 +53,13 @@ pub use runtime::{
RuntimeCargoPriceTarget, RuntimeCargoProductionTarget, RuntimeChairmanMetric,
RuntimeChairmanProfile, RuntimeChairmanTarget, RuntimeCompany,
RuntimeCompanyAnnualCreditorPressureState, RuntimeCompanyAnnualDeepDistressState,
RuntimeCompanyAnnualFinanceState, RuntimeCompanyBondSlot, RuntimeCompanyConditionTestScope,
RuntimeCompanyControllerKind, RuntimeCompanyMarketMetric, RuntimeCompanyMarketState,
RuntimeCompanyMetric, RuntimeCompanyStatBandCandidate, RuntimeCompanyStatSelector,
RuntimeCompanyTarget, RuntimeCompanyTerritoryAccess, RuntimeCompanyTerritoryTrackPieceCount,
RuntimeCondition, RuntimeConditionComparator, RuntimeEffect, RuntimeEventRecord,
RuntimeEventRecordTemplate, RuntimeLocomotiveCatalogEntry, RuntimePackedEventCollectionSummary,
RuntimeCompanyAnnualFinanceState, RuntimeCompanyAnnualStockRepurchaseState,
RuntimeCompanyBondSlot, RuntimeCompanyConditionTestScope, RuntimeCompanyControllerKind,
RuntimeCompanyMarketMetric, RuntimeCompanyMarketState, RuntimeCompanyMetric,
RuntimeCompanyStatBandCandidate, RuntimeCompanyStatSelector, RuntimeCompanyTarget,
RuntimeCompanyTerritoryAccess, RuntimeCompanyTerritoryTrackPieceCount, RuntimeCondition,
RuntimeConditionComparator, RuntimeEffect, RuntimeEventRecord, RuntimeEventRecordTemplate,
RuntimeLocomotiveCatalogEntry, RuntimePackedEventCollectionSummary,
RuntimePackedEventCompactControlSummary, RuntimePackedEventConditionRowSummary,
RuntimePackedEventGroupedEffectRowSummary, RuntimePackedEventNegativeSentinelScopeSummary,
RuntimePackedEventRecordSummary, RuntimePackedEventTextBandSummary, RuntimePlayer,
@ -67,17 +68,18 @@ pub use runtime::{
RuntimeTerritoryTarget, RuntimeTrackMetric, RuntimeTrackPieceCounts, RuntimeTrain,
RuntimeWorldFinanceNeighborhoodCandidate, RuntimeWorldIssueState, RuntimeWorldRestoreState,
runtime_company_annual_creditor_pressure_state, runtime_company_annual_deep_distress_state,
runtime_company_annual_finance_state, runtime_company_assigned_share_pool,
runtime_company_average_live_bond_coupon, runtime_company_book_value_per_share,
runtime_company_credit_rating, runtime_company_investor_confidence,
runtime_company_management_attitude, runtime_company_market_value, runtime_company_prime_rate,
runtime_company_annual_finance_state, runtime_company_annual_stock_repurchase_state,
runtime_company_assigned_share_pool, runtime_company_average_live_bond_coupon,
runtime_company_book_value_per_share, runtime_company_credit_rating,
runtime_company_investor_confidence, runtime_company_management_attitude,
runtime_company_market_value, runtime_company_prime_rate,
runtime_company_recent_per_share_subscore, runtime_company_stat_value,
runtime_company_stat_value_f64, runtime_company_unassigned_share_pool,
runtime_world_annual_finance_mode_active, runtime_world_bankruptcy_allowed,
runtime_world_bond_issue_and_repayment_allowed, runtime_world_dividend_adjustment_allowed,
runtime_world_issue_opinion_multiplier, runtime_world_issue_opinion_term_sum_raw,
runtime_world_issue_state, runtime_world_prime_rate_baseline,
runtime_world_stock_issue_and_buyback_allowed,
runtime_world_bond_issue_and_repayment_allowed, runtime_world_building_density_growth_setting,
runtime_world_dividend_adjustment_allowed, runtime_world_issue_opinion_multiplier,
runtime_world_issue_opinion_term_sum_raw, runtime_world_issue_state,
runtime_world_prime_rate_baseline, runtime_world_stock_issue_and_buyback_allowed,
};
pub use smp::{
SMP_FOUR_SIDECAR_BYTE_PLANES_MIN_BUNDLE_VERSION, SmpAlignedRuntimeRuleBandLane,

View file

@ -227,6 +227,37 @@ pub struct RuntimeCompanyAnnualDeepDistressState {
pub eligible_for_bankruptcy_fallback: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RuntimeCompanyAnnualStockRepurchaseState {
pub company_id: u32,
#[serde(default)]
pub annual_mode_active: Option<bool>,
#[serde(default)]
pub stock_issue_and_buyback_allowed: Option<bool>,
pub city_connection_latch: bool,
#[serde(default)]
pub building_density_growth_setting: Option<u32>,
#[serde(default)]
pub linked_chairman_profile_id: Option<u32>,
#[serde(default)]
pub linked_chairman_personality_raw_u8: Option<u8>,
#[serde(default)]
pub repurchase_batch_size: Option<u32>,
#[serde(default)]
pub repurchase_factor_basis_points: Option<i64>,
#[serde(default)]
pub current_cash: Option<i64>,
#[serde(default)]
pub stock_value_gate_cash_floor: Option<i64>,
#[serde(default)]
pub support_adjusted_share_price_scalar: Option<i64>,
#[serde(default)]
pub affordability_cash_floor: Option<i64>,
#[serde(default)]
pub unassigned_share_pool: Option<u32>,
pub eligible_for_single_batch_repurchase: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct RuntimeTrackPieceCounts {
#[serde(default)]
@ -1044,6 +1075,8 @@ pub struct RuntimeServiceState {
pub company_market_state: BTreeMap<u32, RuntimeCompanyMarketState>,
#[serde(default)]
pub chairman_issue_opinion_terms_raw_i32: BTreeMap<u32, Vec<i32>>,
#[serde(default)]
pub chairman_personality_raw_u8: BTreeMap<u32, u8>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
@ -1159,6 +1192,8 @@ pub struct RuntimeWorldRestoreState {
#[serde(default)]
pub dividend_policy_raw_u8: Option<u8>,
#[serde(default)]
pub building_density_growth_setting_raw_u32: Option<u32>,
#[serde(default)]
pub stock_issue_and_buyback_allowed: Option<bool>,
#[serde(default)]
pub bond_issue_and_repayment_allowed: Option<bool>,
@ -1953,6 +1988,14 @@ impl RuntimeState {
));
}
}
for chairman_profile_id in self.service_state.chairman_personality_raw_u8.keys() {
if !seen_chairman_profile_ids.contains(chairman_profile_id) {
return Err(format!(
"service_state.chairman_personality_raw_u8 references unknown chairman_profile_id {}",
chairman_profile_id
));
}
}
for (player_id, vars) in &self.player_runtime_variables {
if !seen_player_ids.contains(player_id) {
return Err(format!(
@ -2804,6 +2847,95 @@ pub fn runtime_world_dividend_adjustment_allowed(state: &RuntimeState) -> Option
Some(state.world_restore.dividend_policy_raw_u8? == 0)
}
pub fn runtime_world_building_density_growth_setting(state: &RuntimeState) -> Option<u32> {
state.world_restore.building_density_growth_setting_raw_u32
}
fn runtime_chairman_stock_repurchase_factor_f64(
state: &RuntimeState,
chairman_profile_id: Option<u32>,
) -> Option<f64> {
let personality_byte = chairman_profile_id
.and_then(|profile_id| {
state
.service_state
.chairman_personality_raw_u8
.get(&profile_id)
})
.copied();
let mut factor = personality_byte
.map(|byte| (f64::from(byte) * 39.0 + 300.0) / 400.0)
.unwrap_or(1.0);
if runtime_world_building_density_growth_setting(state) == Some(1) {
factor *= 1.6;
}
Some(factor)
}
pub fn runtime_company_annual_stock_repurchase_state(
state: &RuntimeState,
company_id: u32,
) -> Option<RuntimeCompanyAnnualStockRepurchaseState> {
let annual_finance_state = runtime_company_annual_finance_state(state, company_id)?;
let company = state
.companies
.iter()
.find(|company| company.company_id == company_id)?;
let current_cash = runtime_company_control_transfer_stat_value_f64(
state,
company_id,
RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH,
)
.and_then(runtime_round_f64_to_i64);
let support_adjusted_share_price_scalar =
runtime_company_support_adjusted_share_price_scalar_f64(state, company_id);
let repurchase_factor =
runtime_chairman_stock_repurchase_factor_f64(state, company.linked_chairman_profile_id)?;
let repurchase_factor_basis_points = runtime_round_f64_to_i64(repurchase_factor * 100.0);
let stock_value_gate_cash_floor = runtime_round_f64_to_i64(repurchase_factor * 800_000.0);
let affordability_cash_floor = support_adjusted_share_price_scalar
.and_then(|value| runtime_round_f64_to_i64(value * repurchase_factor * 1_000.0 * 1.2));
let support_adjusted_share_price_scalar =
support_adjusted_share_price_scalar.and_then(runtime_round_f64_to_i64);
let unassigned_share_pool = runtime_company_unassigned_share_pool(state, company_id);
let eligible_for_single_batch_repurchase = runtime_world_annual_finance_mode_active(state)
== Some(true)
&& runtime_world_stock_issue_and_buyback_allowed(state) == Some(true)
&& annual_finance_state.city_connection_latch
&& current_cash
.zip(stock_value_gate_cash_floor)
.is_some_and(|(value, floor)| value >= floor)
&& current_cash
.zip(affordability_cash_floor)
.is_some_and(|(value, floor)| value >= floor)
&& unassigned_share_pool.is_some_and(|value| value >= 1_000);
Some(RuntimeCompanyAnnualStockRepurchaseState {
company_id,
annual_mode_active: runtime_world_annual_finance_mode_active(state),
stock_issue_and_buyback_allowed: runtime_world_stock_issue_and_buyback_allowed(state),
city_connection_latch: annual_finance_state.city_connection_latch,
building_density_growth_setting: runtime_world_building_density_growth_setting(state),
linked_chairman_profile_id: company.linked_chairman_profile_id,
linked_chairman_personality_raw_u8: company
.linked_chairman_profile_id
.and_then(|profile_id| {
state
.service_state
.chairman_personality_raw_u8
.get(&profile_id)
})
.copied(),
repurchase_batch_size: Some(1_000),
repurchase_factor_basis_points,
current_cash,
stock_value_gate_cash_floor,
support_adjusted_share_price_scalar,
affordability_cash_floor,
unassigned_share_pool,
eligible_for_single_batch_repurchase,
})
}
pub fn runtime_company_annual_creditor_pressure_state(
state: &RuntimeState,
company_id: u32,
@ -3767,6 +3899,7 @@ mod tests {
bond_issue_and_repayment_policy_raw_u8: None,
bankruptcy_policy_raw_u8: None,
dividend_policy_raw_u8: None,
building_density_growth_setting_raw_u32: None,
stock_issue_and_buyback_allowed: None,
bond_issue_and_repayment_allowed: None,
bankruptcy_allowed: None,
@ -7113,6 +7246,132 @@ mod tests {
assert!(pressure_state.eligible_for_bankruptcy_fallback);
}
#[test]
fn derives_annual_stock_repurchase_state_from_rehosted_owner_state() {
let mut year_stat_family_qword_bits = vec![
0u64;
((RUNTIME_COMPANY_STAT_SLOT_COUNT + 2) * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN)
as usize
];
let write_current_value = |bits: &mut Vec<u64>, slot_id: u32, value: f64| {
let index = (slot_id * RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize;
bits[index] = value.to_bits();
};
write_current_value(&mut year_stat_family_qword_bits, 0x0d, 1_600_000.0);
let state = RuntimeState {
calendar: CalendarPoint {
year: 1845,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState {
partial_year_progress_raw_u8: Some(0x0c),
stock_issue_and_buyback_policy_raw_u8: Some(0),
stock_issue_and_buyback_allowed: Some(true),
building_density_growth_setting_raw_u32: Some(1),
..RuntimeWorldRestoreState::default()
},
metadata: BTreeMap::new(),
companies: vec![RuntimeCompany {
company_id: 12,
current_cash: 0,
debt: 0,
credit_rating_score: None,
prime_rate: None,
active: true,
available_track_laying_capacity: None,
controller_kind: RuntimeCompanyControllerKind::Unknown,
linked_chairman_profile_id: Some(3),
book_value_per_share: 0,
investor_confidence: 0,
management_attitude: 0,
takeover_cooldown_year: None,
merger_cooldown_year: None,
track_piece_counts: RuntimeTrackPieceCounts::default(),
}],
selected_company_id: None,
players: Vec::new(),
selected_player_id: None,
chairman_profiles: vec![RuntimeChairmanProfile {
profile_id: 3,
name: "Jay".to_string(),
active: true,
current_cash: 200,
linked_company_id: Some(12),
company_holdings: BTreeMap::from([(12, 14_500)]),
holdings_value_total: 0,
net_worth_total: 0,
purchasing_power_total: 0,
}],
selected_chairman_profile_id: None,
trains: Vec::new(),
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
packed_event_collection: None,
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: BTreeMap::new(),
all_cargo_price_override: None,
named_cargo_price_overrides: BTreeMap::new(),
all_cargo_production_override: None,
factory_cargo_production_override: None,
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState {
company_market_state: BTreeMap::from([(
12,
RuntimeCompanyMarketState {
outstanding_shares: 20_000,
cached_share_price_raw_u32: 20.0f32.to_bits(),
founding_year: 1835,
city_connection_latch: true,
year_stat_family_qword_bits,
..RuntimeCompanyMarketState::default()
},
)]),
chairman_personality_raw_u8: BTreeMap::from([(3, 20)]),
..RuntimeServiceState::default()
},
};
let repurchase_state = runtime_company_annual_stock_repurchase_state(&state, 12)
.expect("stock repurchase state");
assert_eq!(repurchase_state.building_density_growth_setting, Some(1));
assert_eq!(
repurchase_state.linked_chairman_personality_raw_u8,
Some(20)
);
assert_eq!(repurchase_state.repurchase_batch_size, Some(1_000));
assert_eq!(repurchase_state.repurchase_factor_basis_points, Some(432));
assert_eq!(repurchase_state.current_cash, Some(1_600_000));
assert_eq!(
repurchase_state.stock_value_gate_cash_floor,
Some(3_456_000)
);
assert_eq!(
repurchase_state.support_adjusted_share_price_scalar,
Some(20)
);
assert_eq!(repurchase_state.affordability_cash_floor, Some(103_680));
assert_eq!(repurchase_state.unassigned_share_pool, Some(5_500));
assert!(!repurchase_state.eligible_for_single_batch_repurchase);
}
#[test]
fn reads_company_market_metrics_from_annual_finance_reader() {
let current_issue_calendar_word = 0x0101_0726;

View file

@ -137,6 +137,7 @@ const RT3_SAVE_WORLD_BLOCK_STOCK_POLICY_RELATIVE_OFFSET: usize = 0x4a83;
const RT3_SAVE_WORLD_BLOCK_BOND_POLICY_RELATIVE_OFFSET: usize = 0x4a87;
const RT3_SAVE_WORLD_BLOCK_BANKRUPTCY_POLICY_RELATIVE_OFFSET: usize = 0x4a8b;
const RT3_SAVE_WORLD_BLOCK_DIVIDEND_POLICY_RELATIVE_OFFSET: usize = 0x4a8f;
const RT3_SAVE_WORLD_BLOCK_BUILDING_DENSITY_GROWTH_RELATIVE_OFFSET: usize = 0x4c78;
const RT3_SAVE_WORLD_BLOCK_ECONOMIC_TUNING_MIRROR_RELATIVE_OFFSET: usize = 0x0bda;
const RT3_SAVE_WORLD_BLOCK_ECONOMIC_TUNING_PRIMARY_RELATIVE_OFFSETS: [usize; 6] =
[0x0bde, 0x0be2, 0x0be6, 0x0bea, 0x0bee, 0x0bf2];
@ -1584,6 +1585,7 @@ pub struct SmpSaveWorldFinanceNeighborhoodProbe {
pub bankruptcy_policy_raw_hex: String,
pub dividend_policy_raw_u8: u8,
pub dividend_policy_raw_hex: String,
pub building_density_growth_setting_lane: SmpSaveDwordCandidate,
pub dword_candidates: Vec<SmpSaveDwordCandidate>,
pub evidence: Vec<String>,
}
@ -2276,6 +2278,8 @@ pub struct SmpLoadedWorldFinanceNeighborhoodState {
pub bankruptcy_policy_raw_hex: String,
pub dividend_policy_raw_u8: u8,
pub dividend_policy_raw_hex: String,
pub building_density_growth_setting_raw_u32: u32,
pub building_density_growth_setting_raw_hex: String,
pub labels: Vec<String>,
pub relative_offsets: Vec<usize>,
pub relative_offset_hex: Vec<String>,
@ -2345,6 +2349,8 @@ pub struct SmpLoadedChairmanProfileEntry {
#[serde(default)]
pub purchasing_power_total: i64,
#[serde(default)]
pub personality_byte_0x291: Option<u8>,
#[serde(default)]
pub issue_opinion_terms_raw_i32: Vec<i32>,
}
@ -2457,6 +2463,8 @@ pub struct SmpSaveChairmanRecordAnalysisEntry {
pub derived_net_worth_share_price_total: Option<i64>,
#[serde(default)]
pub derived_cached_purchasing_power_total: Option<i64>,
pub personality_byte_0x291: u8,
pub personality_byte_0x291_hex: String,
#[serde(default)]
pub cached_scalar_candidates: Vec<SmpSaveScalarCandidate>,
}
@ -3255,6 +3263,10 @@ pub fn inspect_save_company_and_chairman_analysis_bytes(
&bytes,
record_offset + SAVE_CHAIRMAN_RECORD_LINKED_COMPANY_OFFSET,
)?;
let personality_byte_0x291 = read_u8_at(
&bytes,
record_offset + SAVE_CHAIRMAN_RECORD_PERSONALITY_BYTE_0X291_OFFSET,
)?;
let mut holdings_by_company = BTreeMap::new();
for company_id in 1..=company_id_bound {
let slot_offset = record_offset
@ -3293,6 +3305,8 @@ pub fn inspect_save_company_and_chairman_analysis_bytes(
derived_holdings_share_price_total,
derived_net_worth_share_price_total,
derived_cached_purchasing_power_total,
personality_byte_0x291,
personality_byte_0x291_hex: format!("0x{personality_byte_0x291:02x}"),
cached_scalar_candidates,
});
}
@ -3528,6 +3542,11 @@ fn derive_loaded_world_finance_neighborhood_state_from_probe(
bankruptcy_policy_raw_hex: probe.bankruptcy_policy_raw_hex.clone(),
dividend_policy_raw_u8: probe.dividend_policy_raw_u8,
dividend_policy_raw_hex: probe.dividend_policy_raw_hex.clone(),
building_density_growth_setting_raw_u32: probe.building_density_growth_setting_lane.raw_u32,
building_density_growth_setting_raw_hex: probe
.building_density_growth_setting_lane
.raw_u32_hex
.clone(),
labels: probe
.dword_candidates
.iter()
@ -3688,6 +3707,7 @@ const SAVE_CHAIRMAN_RECORD_HOLDINGS_BASE_OFFSET: usize = 0x15d;
const SAVE_CHAIRMAN_RECORD_LINKED_COMPANY_OFFSET: usize = 0x1dd;
const SAVE_CHAIRMAN_RECORD_CACHE_0_OFFSET: usize = 0x1e9;
const SAVE_CHAIRMAN_RECORD_CACHE_1_OFFSET: usize = 0x1f1;
const SAVE_CHAIRMAN_RECORD_PERSONALITY_BYTE_0X291_OFFSET: usize = 0x291;
const SAVE_CHAIRMAN_RECORD_ISSUE_OPINION_TERMS_OFFSET: usize = 0x35b;
const SAVE_CHAIRMAN_RECORD_ISSUE_OPINION_TERM_COUNT: usize =
RT3_SAVE_WORLD_BLOCK_ISSUE_OPINION_TERM_COUNT;
@ -4364,6 +4384,10 @@ fn parse_save_chairman_profile_table_probe(
bytes,
record_offset + SAVE_CHAIRMAN_RECORD_LINKED_COMPANY_OFFSET,
)?;
let personality_byte_0x291 = read_u8_at(
bytes,
record_offset + SAVE_CHAIRMAN_RECORD_PERSONALITY_BYTE_0X291_OFFSET,
)?;
let cache_0 = round_f64_to_i64(read_f64_at(
bytes,
record_offset + SAVE_CHAIRMAN_RECORD_CACHE_0_OFFSET,
@ -4409,6 +4433,7 @@ fn parse_save_chairman_profile_table_probe(
holdings_value_total,
net_worth_total,
purchasing_power_total,
personality_byte_0x291: Some(personality_byte_0x291),
issue_opinion_terms_raw_i32,
});
}
@ -9144,6 +9169,12 @@ fn parse_save_world_finance_neighborhood_probe(
bytes,
payload_offset + RT3_SAVE_WORLD_BLOCK_DIVIDEND_POLICY_RELATIVE_OFFSET,
)?;
let building_density_growth_setting_lane = build_save_dword_candidate(
bytes,
payload_offset,
"building_density_growth_setting",
RT3_SAVE_WORLD_BLOCK_BUILDING_DENSITY_GROWTH_RELATIVE_OFFSET,
)?;
let dword_candidates =
build_save_world_finance_neighborhood_candidates(bytes, payload_offset)?;
@ -9171,6 +9202,7 @@ fn parse_save_world_finance_neighborhood_probe(
bankruptcy_policy_raw_hex: format!("0x{bankruptcy_policy_raw_u8:02x}"),
dividend_policy_raw_u8,
dividend_policy_raw_hex: format!("0x{dividend_policy_raw_u8:02x}"),
building_density_growth_setting_lane,
dword_candidates,
evidence: vec![
format!(
@ -9192,6 +9224,10 @@ fn parse_save_world_finance_neighborhood_probe(
RT3_SAVE_WORLD_BLOCK_BANKRUPTCY_POLICY_RELATIVE_OFFSET,
RT3_SAVE_WORLD_BLOCK_DIVIDEND_POLICY_RELATIVE_OFFSET
),
format!(
"payload +0x{:x} carries the fixed-world building-density growth setting mirrored from `[world+0x4c7c]`, which the annual repurchase and dividend policy helpers both read directly",
RT3_SAVE_WORLD_BLOCK_BUILDING_DENSITY_GROWTH_RELATIVE_OFFSET
),
"finance-neighborhood candidates cover the fixed dword strip around the grounded world calendar tuple, absolute-counter, selection-context, and issue-0x37 lanes so broader finance reader closure can build on one rehosted owner surface.".to_string(),
],
});
@ -15931,6 +15967,9 @@ mod tests {
bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_BOND_POLICY_RELATIVE_OFFSET] = 2;
bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_BANKRUPTCY_POLICY_RELATIVE_OFFSET] = 3;
bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_DIVIDEND_POLICY_RELATIVE_OFFSET] = 4;
bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_BUILDING_DENSITY_GROWTH_RELATIVE_OFFSET
..payload_offset + RT3_SAVE_WORLD_BLOCK_BUILDING_DENSITY_GROWTH_RELATIVE_OFFSET + 4]
.copy_from_slice(&2u32.to_le_bytes());
let next_chunk_offset = payload_offset + RT3_SAVE_WORLD_BLOCK_LEN;
bytes[next_chunk_offset..next_chunk_offset + 4]
.copy_from_slice(&RT3_SAVE_WORLD_BLOCK_NEXT_CHUNK_TAG.to_le_bytes());
@ -15968,6 +16007,13 @@ mod tests {
assert_eq!(probe.bankruptcy_policy_raw_hex, "0x03");
assert_eq!(probe.dividend_policy_raw_u8, 4);
assert_eq!(probe.dividend_policy_raw_hex, "0x04");
assert_eq!(probe.building_density_growth_setting_lane.raw_u32, 2);
assert_eq!(
probe
.building_density_growth_setting_lane
.relative_offset_hex,
"0x4c78"
);
assert_eq!(probe.current_calendar_tuple_word_lane.value_i32, 1);
assert_eq!(
probe.current_calendar_tuple_word_2_lane.relative_offset_hex,
@ -16773,6 +16819,8 @@ mod tests {
bytes[record_offset + SAVE_CHAIRMAN_RECORD_LINKED_COMPANY_OFFSET
..record_offset + SAVE_CHAIRMAN_RECORD_LINKED_COMPANY_OFFSET + 4]
.copy_from_slice(&linked.to_le_bytes());
bytes[record_offset + SAVE_CHAIRMAN_RECORD_PERSONALITY_BYTE_0X291_OFFSET] =
(index as u8) + 10;
bytes[record_offset + SAVE_CHAIRMAN_RECORD_CACHE_0_OFFSET
..record_offset + SAVE_CHAIRMAN_RECORD_CACHE_0_OFFSET + 8]
.copy_from_slice(&cache0.to_le_bytes());
@ -16857,10 +16905,12 @@ mod tests {
assert_eq!(table.entries[0].current_cash, -107644);
assert_eq!(table.entries[0].holdings_value_total, 252508);
assert_eq!(table.entries[0].purchasing_power_total, 144864);
assert_eq!(table.entries[0].personality_byte_0x291, Some(10));
assert_eq!(table.entries[1].profile_id, 2);
assert_eq!(table.entries[1].company_holdings.get(&2), Some(&9000));
assert_eq!(table.entries[1].holdings_value_total, 822000);
assert_eq!(table.entries[1].purchasing_power_total, 1_009_282);
assert_eq!(table.entries[1].personality_byte_0x291, Some(11));
}
#[test]

View file

@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
use crate::{
CalendarPoint, RuntimeState, runtime_company_annual_creditor_pressure_state,
runtime_company_annual_deep_distress_state, runtime_company_annual_finance_state,
runtime_company_unassigned_share_pool,
runtime_company_annual_stock_repurchase_state, runtime_company_unassigned_share_pool,
};
fn raw_u32_to_f32_text(raw: u32) -> String {
@ -55,6 +55,7 @@ pub struct RuntimeSummary {
pub world_restore_bond_issue_and_repayment_policy_raw_u8: Option<u8>,
pub world_restore_bankruptcy_policy_raw_u8: Option<u8>,
pub world_restore_dividend_policy_raw_u8: Option<u8>,
pub world_restore_building_density_growth_setting_raw_u32: Option<u32>,
pub world_restore_stock_issue_and_buyback_allowed: Option<bool>,
pub world_restore_bond_issue_and_repayment_allowed: Option<bool>,
pub world_restore_bankruptcy_allowed: Option<bool>,
@ -111,6 +112,18 @@ pub struct RuntimeSummary {
pub selected_company_deep_distress_cash_floor: Option<i64>,
pub selected_company_deep_distress_net_profit_floor: Option<i64>,
pub selected_company_deep_distress_eligible_for_bankruptcy_fallback: Option<bool>,
pub selected_company_stock_repurchase_city_connection_latch: Option<bool>,
pub selected_company_stock_repurchase_building_density_growth_setting: Option<u32>,
pub selected_company_stock_repurchase_linked_chairman_profile_id: Option<u32>,
pub selected_company_stock_repurchase_linked_chairman_personality_raw_u8: Option<u8>,
pub selected_company_stock_repurchase_batch_size: Option<u32>,
pub selected_company_stock_repurchase_factor_basis_points: Option<i64>,
pub selected_company_stock_repurchase_current_cash: Option<i64>,
pub selected_company_stock_repurchase_stock_value_gate_cash_floor: Option<i64>,
pub selected_company_stock_repurchase_support_adjusted_share_price_scalar: Option<i64>,
pub selected_company_stock_repurchase_affordability_cash_floor: Option<i64>,
pub selected_company_stock_repurchase_unassigned_share_pool: Option<u32>,
pub selected_company_stock_repurchase_eligible_for_single_batch: Option<bool>,
pub player_count: usize,
pub chairman_profile_count: usize,
pub active_chairman_profile_count: usize,
@ -207,6 +220,10 @@ impl RuntimeSummary {
let selected_company_deep_distress_state = state
.selected_company_id
.and_then(|company_id| runtime_company_annual_deep_distress_state(state, company_id));
let selected_company_stock_repurchase_state =
state.selected_company_id.and_then(|company_id| {
runtime_company_annual_stock_repurchase_state(state, company_id)
});
Self {
calendar: state.calendar,
calendar_projection_source: state.metadata.get("save_slice.calendar_source").cloned(),
@ -294,6 +311,9 @@ impl RuntimeSummary {
.bond_issue_and_repayment_policy_raw_u8,
world_restore_bankruptcy_policy_raw_u8: state.world_restore.bankruptcy_policy_raw_u8,
world_restore_dividend_policy_raw_u8: state.world_restore.dividend_policy_raw_u8,
world_restore_building_density_growth_setting_raw_u32: state
.world_restore
.building_density_growth_setting_raw_u32,
world_restore_stock_issue_and_buyback_allowed: state
.world_restore
.stock_issue_and_buyback_allowed,
@ -480,6 +500,56 @@ impl RuntimeSummary {
selected_company_deep_distress_state
.as_ref()
.map(|pressure_state| pressure_state.eligible_for_bankruptcy_fallback),
selected_company_stock_repurchase_city_connection_latch:
selected_company_stock_repurchase_state
.as_ref()
.map(|repurchase_state| repurchase_state.city_connection_latch),
selected_company_stock_repurchase_building_density_growth_setting:
selected_company_stock_repurchase_state
.as_ref()
.and_then(|repurchase_state| repurchase_state.building_density_growth_setting),
selected_company_stock_repurchase_linked_chairman_profile_id:
selected_company_stock_repurchase_state
.as_ref()
.and_then(|repurchase_state| repurchase_state.linked_chairman_profile_id),
selected_company_stock_repurchase_linked_chairman_personality_raw_u8:
selected_company_stock_repurchase_state
.as_ref()
.and_then(|repurchase_state| {
repurchase_state.linked_chairman_personality_raw_u8
}),
selected_company_stock_repurchase_batch_size: selected_company_stock_repurchase_state
.as_ref()
.and_then(|repurchase_state| repurchase_state.repurchase_batch_size),
selected_company_stock_repurchase_factor_basis_points:
selected_company_stock_repurchase_state
.as_ref()
.and_then(|repurchase_state| repurchase_state.repurchase_factor_basis_points),
selected_company_stock_repurchase_current_cash: selected_company_stock_repurchase_state
.as_ref()
.and_then(|repurchase_state| repurchase_state.current_cash),
selected_company_stock_repurchase_stock_value_gate_cash_floor:
selected_company_stock_repurchase_state
.as_ref()
.and_then(|repurchase_state| repurchase_state.stock_value_gate_cash_floor),
selected_company_stock_repurchase_support_adjusted_share_price_scalar:
selected_company_stock_repurchase_state
.as_ref()
.and_then(|repurchase_state| {
repurchase_state.support_adjusted_share_price_scalar
}),
selected_company_stock_repurchase_affordability_cash_floor:
selected_company_stock_repurchase_state
.as_ref()
.and_then(|repurchase_state| repurchase_state.affordability_cash_floor),
selected_company_stock_repurchase_unassigned_share_pool:
selected_company_stock_repurchase_state
.as_ref()
.and_then(|repurchase_state| repurchase_state.unassigned_share_pool),
selected_company_stock_repurchase_eligible_for_single_batch:
selected_company_stock_repurchase_state
.as_ref()
.map(|repurchase_state| repurchase_state.eligible_for_single_batch_repurchase),
player_count: state.players.len(),
chairman_profile_count: state.chairman_profiles.len(),
active_chairman_profile_count: state
@ -2590,4 +2660,155 @@ mod tests {
Some(true)
);
}
#[test]
fn summarizes_selected_company_stock_repurchase_state() {
let mut year_stat_family_qword_bits = vec![
0u64;
((crate::RUNTIME_COMPANY_STAT_SLOT_COUNT + 2)
* crate::RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN)
as usize
];
let write_current_value = |bits: &mut Vec<u64>, slot_id: u32, value: f64| {
let index = (slot_id * crate::RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN) as usize;
bits[index] = value.to_bits();
};
write_current_value(&mut year_stat_family_qword_bits, 0x0d, 1_600_000.0);
let state = RuntimeState {
calendar: CalendarPoint {
year: 1845,
month_slot: 0,
phase_slot: 0,
tick_slot: 0,
},
world_flags: BTreeMap::new(),
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState {
partial_year_progress_raw_u8: Some(0x0c),
stock_issue_and_buyback_policy_raw_u8: Some(0),
stock_issue_and_buyback_allowed: Some(true),
building_density_growth_setting_raw_u32: Some(1),
..RuntimeWorldRestoreState::default()
},
metadata: BTreeMap::new(),
companies: vec![RuntimeCompany {
company_id: 12,
current_cash: 0,
debt: 0,
credit_rating_score: None,
prime_rate: None,
active: true,
available_track_laying_capacity: None,
controller_kind: crate::RuntimeCompanyControllerKind::Unknown,
linked_chairman_profile_id: Some(3),
book_value_per_share: 0,
investor_confidence: 0,
management_attitude: 0,
takeover_cooldown_year: None,
merger_cooldown_year: None,
track_piece_counts: RuntimeTrackPieceCounts::default(),
}],
selected_company_id: Some(12),
players: Vec::new(),
selected_player_id: None,
chairman_profiles: vec![crate::RuntimeChairmanProfile {
profile_id: 3,
name: "Jay".to_string(),
active: true,
current_cash: 200,
linked_company_id: Some(12),
company_holdings: BTreeMap::from([(12, 14_500)]),
holdings_value_total: 0,
net_worth_total: 0,
purchasing_power_total: 0,
}],
selected_chairman_profile_id: None,
trains: Vec::new(),
locomotive_catalog: Vec::new(),
cargo_catalog: Vec::new(),
territories: Vec::new(),
company_territory_track_piece_counts: Vec::new(),
company_territory_access: Vec::new(),
packed_event_collection: None,
event_runtime_records: Vec::new(),
candidate_availability: BTreeMap::new(),
named_locomotive_availability: BTreeMap::new(),
named_locomotive_cost: Vec::new().into_iter().collect(),
all_cargo_price_override: None,
named_cargo_price_overrides: BTreeMap::new(),
all_cargo_production_override: None,
factory_cargo_production_override: None,
farm_mine_cargo_production_override: None,
named_cargo_production_overrides: BTreeMap::new(),
cargo_production_overrides: BTreeMap::new(),
world_runtime_variables: BTreeMap::new(),
company_runtime_variables: BTreeMap::new(),
player_runtime_variables: BTreeMap::new(),
territory_runtime_variables: BTreeMap::new(),
world_scalar_overrides: BTreeMap::new(),
special_conditions: BTreeMap::new(),
service_state: RuntimeServiceState {
company_market_state: BTreeMap::from([(
12,
crate::RuntimeCompanyMarketState {
outstanding_shares: 20_000,
cached_share_price_raw_u32: 20.0f32.to_bits(),
founding_year: 1835,
city_connection_latch: true,
year_stat_family_qword_bits,
..crate::RuntimeCompanyMarketState::default()
},
)]),
chairman_personality_raw_u8: BTreeMap::from([(3, 20)]),
..RuntimeServiceState::default()
},
};
let summary = RuntimeSummary::from_state(&state);
assert_eq!(
summary.world_restore_building_density_growth_setting_raw_u32,
Some(1)
);
assert_eq!(
summary.selected_company_stock_repurchase_building_density_growth_setting,
Some(1)
);
assert_eq!(
summary.selected_company_stock_repurchase_linked_chairman_personality_raw_u8,
Some(20)
);
assert_eq!(
summary.selected_company_stock_repurchase_batch_size,
Some(1_000)
);
assert_eq!(
summary.selected_company_stock_repurchase_factor_basis_points,
Some(432)
);
assert_eq!(
summary.selected_company_stock_repurchase_current_cash,
Some(1_600_000)
);
assert_eq!(
summary.selected_company_stock_repurchase_stock_value_gate_cash_floor,
Some(3_456_000)
);
assert_eq!(
summary.selected_company_stock_repurchase_support_adjusted_share_price_scalar,
Some(20)
);
assert_eq!(
summary.selected_company_stock_repurchase_affordability_cash_floor,
Some(103_680)
);
assert_eq!(
summary.selected_company_stock_repurchase_unassigned_share_pool,
Some(5_500)
);
assert_eq!(
summary.selected_company_stock_repurchase_eligible_for_single_batch,
Some(false)
);
}
}

View file

@ -138,7 +138,9 @@ The highest-value next passes are now:
bankruptcy, and dividend finance-policy bytes, and the first annual creditor-pressure branch now
executes as a pure runtime reader over that owner state instead of remaining atlas-only; the
later deep-distress bankruptcy fallback now runs on that same save-native cash and trailing-
net-profit surface too
net-profit surface too; the same owner seam now also carries the fixed-world building-density
growth setting plus the linked chairman personality byte, which is enough to run the annual
stock-repurchase gate headlessly as another pure reader
- 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
instead of guessing another derived leaf field from neighboring raw offsets

View file

@ -231,6 +231,9 @@ bankruptcy branch now runs as a pure runtime reader over owned annual-finance st
adjusted share price, and those policy bytes rather than staying in atlas prose only. The later
deep-distress bankruptcy fallback now rides the same owner-state seam too, using the save-native
cash reader plus the first three trailing net-profit years instead of a parallel raw-offset guess.
That same seam now also carries the fixed-world building-density growth setting plus the linked
chairman personality byte, which is enough to rehost the annual stock-repurchase gate on owned
save/runtime state instead of another threshold-only note.
## Why This Boundary