use super::*; pub(super) fn maybe_emit_finance_template_bundle() { if env::var_os("RRT_WRITE_FINANCE_TEMPLATE").is_none() { return; } if FINANCE_TEMPLATE_EMITTED.swap(true, Ordering::AcqRel) { return; } let base_dir = env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); let _ = write_finance_snapshot_bundle(&base_dir, "attach_template", &sample_finance_snapshot()); } pub(super) fn maybe_start_finance_capture_thread() { if env::var_os("RRT_WRITE_FINANCE_CAPTURE").is_none() { return; } if FINANCE_CAPTURE_STARTED.swap(true, Ordering::AcqRel) { return; } append_log_message(FINANCE_CAPTURE_STARTED_MESSAGE); let base_dir = env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); let _ = thread::Builder::new() .name("rrt-finance-capture".to_string()) .spawn(move || { for _ in 0..MAX_CAPTURE_POLL_ATTEMPTS { if !FINANCE_COLLECTION_PROBE_WRITTEN.load(Ordering::Acquire) { if let Some(probe) = unsafe { capture_company_collection_probe() } { if write_indexed_collection_probe(&base_dir, "attach_probe", &probe).is_ok() { FINANCE_COLLECTION_PROBE_WRITTEN.store(true, Ordering::Release); append_log_message(FINANCE_CAPTURE_PROBE_DUMP_WRITTEN_MESSAGE); } } } if let Some(snapshot) = unsafe { try_capture_probe_snapshot() } { append_log_message(FINANCE_CAPTURE_COMPANY_RESOLVED_MESSAGE); if write_finance_snapshot_only(&base_dir, "attach_probe", &snapshot).is_ok() { append_log_message(FINANCE_CAPTURE_PROBE_WRITTEN_MESSAGE); return; } } thread::sleep(CAPTURE_POLL_INTERVAL); } append_log_message(FINANCE_CAPTURE_TIMEOUT_MESSAGE); }); } pub(super) fn maybe_start_cargo_capture_thread() { if env::var_os("RRT_WRITE_CARGO_CAPTURE").is_none() { return; } if CARGO_CAPTURE_STARTED.swap(true, Ordering::AcqRel) { return; } append_log_message(CARGO_CAPTURE_STARTED_MESSAGE); let base_dir = env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); let _ = thread::Builder::new() .name("rrt-cargo-capture".to_string()) .spawn(move || { let mut last_probe: Option = None; for _ in 0..MAX_CAPTURE_POLL_ATTEMPTS { if let Some(probe) = unsafe { capture_cargo_collection_probe() } { last_probe = Some(probe.clone()); if probe.live_entry_count > 0 && write_cargo_collection_probe(&base_dir, "attach_probe", &probe).is_ok() { append_log_message(CARGO_CAPTURE_WRITTEN_MESSAGE); return; } } thread::sleep(CAPTURE_POLL_INTERVAL); } if let Some(probe) = last_probe { let _ = write_cargo_collection_probe(&base_dir, "attach_probe_timeout", &probe); } append_log_message(CARGO_CAPTURE_TIMEOUT_MESSAGE); }); } pub(super) unsafe fn try_capture_probe_snapshot() -> Option { append_log_message(FINANCE_CAPTURE_SCAN_MESSAGE); let company = unsafe { resolve_first_active_company()? }; Some(unsafe { capture_probe_snapshot_from_company(company) }) } pub(super) unsafe fn resolve_first_active_company() -> Option<*mut u8> { let collection = COMPANY_COLLECTION_ADDR as *const u8; let id_bound = unsafe { read_i32(collection.add(INDEXED_COLLECTION_ID_BOUND_OFFSET)) }; if id_bound <= 0 { return None; } for entry_id in 1..=id_bound as usize { if unsafe { indexed_collection_entry_id_is_live(collection, entry_id) } { let company = unsafe { indexed_collection_resolve_live_entry_by_id(collection, entry_id) }; if !company.is_null() && unsafe { read_u8(company.add(COMPANY_ACTIVE_OFFSET)) != 0 } { return Some(company); } } } None } pub(super) unsafe fn capture_company_collection_probe() -> Option { let collection = COMPANY_COLLECTION_ADDR as *const u8; let id_bound = unsafe { read_i32(collection.add(INDEXED_COLLECTION_ID_BOUND_OFFSET)) }; if id_bound <= 0 { return Some(IndexedCollectionProbe { collection_addr: COMPANY_COLLECTION_ADDR, flat_payload: unsafe { read_u32(collection.add(INDEXED_COLLECTION_FLAT_FLAG_OFFSET)) != 0 }, stride: unsafe { read_u32(collection.add(INDEXED_COLLECTION_STRIDE_OFFSET)) }, id_bound, payload_ptr: unsafe { read_ptr(collection.add(INDEXED_COLLECTION_PAYLOAD_OFFSET)) as usize }, tombstone_ptr: unsafe { read_ptr(collection.add(INDEXED_COLLECTION_TOMBSTONE_BITSET_OFFSET)) as usize }, first_rows: Vec::new(), }); } let mut first_rows = Vec::new(); let sample_bound = (id_bound as usize).min(8); for entry_id in 1..=sample_bound { let live = unsafe { indexed_collection_entry_id_is_live(collection, entry_id) }; let resolved_ptr = unsafe { indexed_collection_resolve_live_entry_by_id(collection, entry_id) as usize }; let active_flag = if resolved_ptr == 0 { None } else { Some(unsafe { read_u8((resolved_ptr as *const u8).add(COMPANY_ACTIVE_OFFSET)) }) }; first_rows.push(IndexedCollectionProbeRow { entry_id, live, resolved_ptr, active_flag, }); } Some(IndexedCollectionProbe { collection_addr: COMPANY_COLLECTION_ADDR, flat_payload: unsafe { read_u32(collection.add(INDEXED_COLLECTION_FLAT_FLAG_OFFSET)) != 0 }, stride: unsafe { read_u32(collection.add(INDEXED_COLLECTION_STRIDE_OFFSET)) }, id_bound, payload_ptr: unsafe { read_ptr(collection.add(INDEXED_COLLECTION_PAYLOAD_OFFSET)) as usize }, tombstone_ptr: unsafe { read_ptr(collection.add(INDEXED_COLLECTION_TOMBSTONE_BITSET_OFFSET)) as usize }, first_rows, }) } pub(super) unsafe fn capture_cargo_collection_probe() -> Option { let collection = CARGO_COLLECTION_ADDR as *const u8; let id_bound = unsafe { read_i32(collection.add(INDEXED_COLLECTION_ID_BOUND_OFFSET)) }; if id_bound <= 0 { return Some(CargoCollectionProbe { collection_addr: CARGO_COLLECTION_ADDR, flat_payload: unsafe { read_u32(collection.add(INDEXED_COLLECTION_FLAT_FLAG_OFFSET)) != 0 }, stride: unsafe { read_u32(collection.add(INDEXED_COLLECTION_STRIDE_OFFSET)) }, id_bound, payload_ptr: unsafe { read_ptr(collection.add(INDEXED_COLLECTION_PAYLOAD_OFFSET)) as usize }, tombstone_ptr: unsafe { read_ptr(collection.add(INDEXED_COLLECTION_TOMBSTONE_BITSET_OFFSET)) as usize }, live_entry_count: 0, rows: Vec::new(), }); } let mut live_entry_count = 0_usize; let mut rows = Vec::with_capacity(id_bound as usize); for entry_id in 1..=id_bound as usize { let live = unsafe { indexed_collection_entry_id_is_live(collection, entry_id) }; let resolved_ptr = unsafe { indexed_collection_resolve_live_entry_by_id(collection, entry_id) as usize }; if live && resolved_ptr != 0 { live_entry_count += 1; } let stem = if resolved_ptr == 0 { None } else { Some(unsafe { read_c_string((resolved_ptr as *const u8).add(CARGO_STEM_OFFSET), 0x1e) }) }; let route_style_byte = if resolved_ptr == 0 { None } else { Some(unsafe { read_u8((resolved_ptr as *const u8).add(CARGO_ROUTE_STYLE_OFFSET)) }) }; let subtype_byte = if resolved_ptr == 0 { None } else { Some(unsafe { read_u8((resolved_ptr as *const u8).add(CARGO_SUBTYPE_OFFSET)) }) }; let class_marker = if live { Some(unsafe { read_u32(collection.add(CARGO_COLLECTION_CLASS_MARKER_BASE_OFFSET + entry_id * 4)) }) } else { None }; rows.push(CargoCollectionProbeRow { entry_id, live, resolved_ptr, stem, route_style_byte, subtype_byte, class_marker, }); } Some(CargoCollectionProbe { collection_addr: CARGO_COLLECTION_ADDR, flat_payload: unsafe { read_u32(collection.add(INDEXED_COLLECTION_FLAT_FLAG_OFFSET)) != 0 }, stride: unsafe { read_u32(collection.add(INDEXED_COLLECTION_STRIDE_OFFSET)) }, id_bound, payload_ptr: unsafe { read_ptr(collection.add(INDEXED_COLLECTION_PAYLOAD_OFFSET)) as usize }, tombstone_ptr: unsafe { read_ptr(collection.add(INDEXED_COLLECTION_TOMBSTONE_BITSET_OFFSET)) as usize }, live_entry_count, rows, }) } pub(super) unsafe fn capture_probe_snapshot_from_company(company: *mut u8) -> FinanceSnapshot { let scenario = unsafe { read_ptr(ACTIVE_MODE_PTR_ADDR as *const u8) } as *const u8; let current_year = unsafe { read_u16(scenario.add(SCENARIO_CURRENT_YEAR_OFFSET)) }; let founding_year = unsafe { read_u16(company.add(COMPANY_FOUNDING_YEAR_OFFSET)) }; let last_bankruptcy_year = unsafe { read_u16(company.add(COMPANY_LAST_BANKRUPTCY_YEAR_OFFSET)) }; let outstanding_share_count = unsafe { read_u32(company.add(COMPANY_OUTSTANDING_SHARES_OFFSET)) }; let bonds = unsafe { capture_bonds(company, current_year) }; let company_value = unsafe { read_u32(company.add(COMPANY_COMPANY_VALUE_OFFSET)) as i64 }; let growth_setting = unsafe { growth_setting_from_raw(read_u8( scenario.add(SCENARIO_BUILDING_DENSITY_GROWTH_OFFSET), )) }; FinanceSnapshot { policy: AnnualFinancePolicy { annual_mode: 0x0c, bankruptcy_allowed: unsafe { read_u8(scenario.add(SCENARIO_BANKRUPTCY_TOGGLE_OFFSET)) == 0 }, bond_issuance_allowed: unsafe { read_u8(scenario.add(SCENARIO_BOND_TOGGLE_OFFSET)) == 0 }, stock_actions_allowed: unsafe { read_u8(scenario.add(SCENARIO_STOCK_TOGGLE_OFFSET)) == 0 }, dividends_allowed: unsafe { read_u8(scenario.add(SCENARIO_DIVIDEND_TOGGLE_OFFSET)) == 0 }, growth_setting, ..AnnualFinancePolicy::default() }, company: CompanyFinanceState { active: unsafe { read_u8(company.add(COMPANY_ACTIVE_OFFSET)) != 0 }, years_since_founding: year_delta(current_year, founding_year), years_since_last_bankruptcy: year_delta(current_year, last_bankruptcy_year), current_company_value: company_value, outstanding_share_count, city_connection_bonus_latch: unsafe { read_u8(company.add(COMPANY_CITY_CONNECTION_LATCH_OFFSET)) != 0 }, linked_transit_service_latch: unsafe { read_u8(company.add(COMPANY_LINKED_TRANSIT_LATCH_OFFSET)) != 0 }, chairman_buyback_factor: None, bonds, ..CompanyFinanceState::default() }, } } pub(super) unsafe fn capture_bonds(company: *mut u8, current_year: u16) -> Vec { let bond_count = unsafe { read_u8(company.add(COMPANY_BOND_COUNT_OFFSET)) as usize }; let table = unsafe { company.add(COMPANY_BOND_TABLE_OFFSET) }; let mut bonds = Vec::with_capacity(bond_count); for index in 0..bond_count { let slot = unsafe { table.add(index * 12) }; let principal = unsafe { read_i32(slot) } as i64; let maturity_year = unsafe { read_u32(slot.add(4)) }; let coupon_rate = unsafe { read_f32(slot.add(8)) } as f64; bonds.push(BondPosition { principal, coupon_rate, years_remaining: maturity_year .saturating_sub(current_year as u32) .min(u8::MAX as u32) as u8, }); } bonds } pub(super) fn growth_setting_from_raw(raw: u8) -> GrowthSetting { match raw { 1 => GrowthSetting::ExpansionBias, 2 => GrowthSetting::DividendSuppressed, _ => GrowthSetting::Neutral, } } pub(super) fn year_delta(current_year: u16, past_year: u16) -> u8 { current_year.saturating_sub(past_year).min(u8::MAX as u16) as u8 }