Probe save-world economic tuning and chairman power
This commit is contained in:
parent
1525703cd1
commit
a1ace18a0d
5 changed files with 328 additions and 13 deletions
|
|
@ -34,12 +34,17 @@ supplies selected company/chairman ids plus the campaign override byte and chair
|
|||
analysis bytes, and the tagged company / chairman-profile direct-record families now populate
|
||||
save-native roster entries for real `.gms` imports and exports. The current raw-save boundary is
|
||||
narrower now: company/chairman identity, active flags, links, chairman cash, chairman holdings,
|
||||
company debt, and company track-laying capacity are grounded directly from save records, while
|
||||
chairman purchasing power, company debt, and company track-laying capacity are grounded directly
|
||||
from save records, while
|
||||
broader company finance/governance scalars and controller-kind reconstruction still remain
|
||||
conservative defaults until their raw lanes are pinned more strongly. The offline runtime analysis
|
||||
surface also now exposes `runtime inspect-save-company-chairman <save.gms>` for those remaining raw
|
||||
company/chairman scalar candidates, including fixed-world chairman slot / role-gate context,
|
||||
explicit company dword candidate windows, and richer chairman qword cache views. A checked-in
|
||||
explicit company dword candidate windows, richer chairman qword cache views, and derived
|
||||
holdings-at-share-price / cached purchasing-power comparisons. The same fixed `0x32c8` world
|
||||
block is now probed for its six-float economic tuning band too, but current atlas evidence still
|
||||
keeps that editor-facing tuning family separate from the governance issue lanes behind investor
|
||||
confidence and prime-rate math. A checked-in
|
||||
`EventEffects` export now exists too in
|
||||
`artifacts/exports/rt3-1.06/event-effects-table.json`, and a checked-in semantic closure layer now
|
||||
exists beside it in `artifacts/exports/rt3-1.06/event-effects-semantic-catalog.json`. Recovered
|
||||
|
|
|
|||
|
|
@ -85,8 +85,9 @@ pub use smp::{
|
|||
SmpSaveChairmanRecordAnalysisEntry, SmpSaveCompanyChairmanAnalysisReport,
|
||||
SmpSaveCompanyRecordAnalysisEntry, SmpSaveDwordCandidate, SmpSaveLoadCandidateTableSummary,
|
||||
SmpSaveLoadSummary, SmpSaveScalarCandidate, SmpSaveTaggedCollectionHeaderProbe,
|
||||
SmpSaveWorldSelectionRoleAnalysis, SmpSaveWorldSelectionRoleAnalysisEntry,
|
||||
SmpSecondaryVariantProbe, SmpSharedHeader, SmpSpecialConditionEntry, SmpSpecialConditionsProbe,
|
||||
SmpSaveWorldEconomicTuningProbe, SmpSaveWorldSelectionRoleAnalysis,
|
||||
SmpSaveWorldSelectionRoleAnalysisEntry, SmpSecondaryVariantProbe, SmpSharedHeader,
|
||||
SmpSpecialConditionEntry, SmpSpecialConditionsProbe,
|
||||
inspect_save_company_and_chairman_analysis_bytes,
|
||||
inspect_save_company_and_chairman_analysis_file, inspect_smp_bytes, inspect_smp_file,
|
||||
load_save_slice_file, load_save_slice_from_report,
|
||||
|
|
|
|||
|
|
@ -98,6 +98,9 @@ const RT3_SAVE_WORLD_BLOCK_SELECTED_CHAIRMAN_PROFILE_ID_RELATIVE_OFFSET: usize =
|
|||
const RT3_SAVE_WORLD_BLOCK_CHAIRMAN_SLOT_SELECTOR_RELATIVE_OFFSET: usize = 0x83;
|
||||
const RT3_SAVE_WORLD_BLOCK_CAMPAIGN_OVERRIDE_FLAG_RELATIVE_OFFSET: usize = 0xc1;
|
||||
const RT3_SAVE_WORLD_BLOCK_CHAIRMAN_ROLE_GATE_RELATIVE_OFFSET: usize = 0x0bbf;
|
||||
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];
|
||||
const RT3_SAVE_WORLD_BLOCK_CHAIRMAN_SLOT_COUNT: usize = 16;
|
||||
const RT3_SAVE_WORLD_BLOCK_CHAIRMAN_ROLE_GATE_STRIDE: usize = 9;
|
||||
const EVENT_RUNTIME_COLLECTION_METADATA_TAG: u16 = 0x4e99;
|
||||
|
|
@ -1479,6 +1482,20 @@ pub struct SmpSaveWorldSelectionContextProbe {
|
|||
pub evidence: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct SmpSaveWorldEconomicTuningProbe {
|
||||
pub profile_family: String,
|
||||
pub source_kind: String,
|
||||
pub semantic_family: String,
|
||||
pub chunk_tag_offset: usize,
|
||||
pub payload_offset: usize,
|
||||
pub payload_len: usize,
|
||||
pub payload_len_hex: String,
|
||||
pub mirror_lane: SmpSaveDwordCandidate,
|
||||
pub tuning_lanes: Vec<SmpSaveDwordCandidate>,
|
||||
pub evidence: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SmpSaveTaggedCollectionHeaderProbe {
|
||||
pub profile_family: String,
|
||||
|
|
@ -2260,6 +2277,12 @@ pub struct SmpSaveChairmanRecordAnalysisEntry {
|
|||
#[serde(default)]
|
||||
pub holdings_by_company: BTreeMap<u32, u32>,
|
||||
#[serde(default)]
|
||||
pub derived_holdings_share_price_total: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub derived_net_worth_share_price_total: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub derived_cached_purchasing_power_total: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub cached_scalar_candidates: Vec<SmpSaveScalarCandidate>,
|
||||
}
|
||||
|
||||
|
|
@ -2273,6 +2296,8 @@ pub struct SmpSaveCompanyChairmanAnalysisReport {
|
|||
#[serde(default)]
|
||||
pub world_selection_context: Option<SmpSaveWorldSelectionRoleAnalysis>,
|
||||
#[serde(default)]
|
||||
pub world_economic_tuning: Option<SmpSaveWorldEconomicTuningProbe>,
|
||||
#[serde(default)]
|
||||
pub company_entries: Vec<SmpSaveCompanyRecordAnalysisEntry>,
|
||||
#[serde(default)]
|
||||
pub chairman_entries: Vec<SmpSaveChairmanRecordAnalysisEntry>,
|
||||
|
|
@ -2497,7 +2522,7 @@ pub struct SmpPackedProfileWordLane {
|
|||
pub value_hex: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct SmpInspectionReport {
|
||||
pub inspection_mode: String,
|
||||
pub file_extension_hint: Option<String>,
|
||||
|
|
@ -2518,6 +2543,7 @@ pub struct SmpInspectionReport {
|
|||
pub rt3_105_post_span_bridge_probe: Option<SmpRt3105PostSpanBridgeProbe>,
|
||||
pub rt3_105_save_bridge_payload_probe: Option<SmpRt3105SaveBridgePayloadProbe>,
|
||||
pub save_world_selection_context_probe: Option<SmpSaveWorldSelectionContextProbe>,
|
||||
pub save_world_economic_tuning_probe: Option<SmpSaveWorldEconomicTuningProbe>,
|
||||
pub save_company_collection_header_probe: Option<SmpSaveTaggedCollectionHeaderProbe>,
|
||||
pub save_chairman_profile_collection_header_probe: Option<SmpSaveTaggedCollectionHeaderProbe>,
|
||||
#[serde(default)]
|
||||
|
|
@ -2720,10 +2746,24 @@ pub fn load_save_slice_from_report(
|
|||
{
|
||||
notes.push(
|
||||
"Raw save inspection still does not reconstruct every company_roster or chairman_profile_table scalar lane; the grounded package-save path prefers direct-record reconstruction where it can and falls back to selection/header-only context otherwise."
|
||||
.to_string(),
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
}
|
||||
if let Some(probe) = &report.save_world_economic_tuning_probe {
|
||||
notes.push(format!(
|
||||
"Raw save fixed world block also exposes the six-lane economic tuning float band at file offset 0x{:x} (mirror lane at 0x{:x}).",
|
||||
probe.tuning_lanes
|
||||
.first()
|
||||
.map(|lane| probe.payload_offset + lane.relative_offset)
|
||||
.unwrap_or(probe.payload_offset),
|
||||
probe.payload_offset + probe.mirror_lane.relative_offset
|
||||
));
|
||||
notes.push(
|
||||
"Current atlas evidence treats that fixed six-float world tuning band as the editor economic-cost family, not as the company-governance issue table behind investor confidence."
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
if let Some(probe) = &report.save_company_collection_header_probe {
|
||||
notes.push(format!(
|
||||
"Raw save tagged company header reports live_record_count={} and live_id_bound={} at file offsets 0x{:x}/0x{:x}/0x{:x}.",
|
||||
|
|
@ -2797,6 +2837,7 @@ pub fn inspect_save_company_and_chairman_analysis_bytes(
|
|||
) -> Option<SmpSaveCompanyChairmanAnalysisReport> {
|
||||
let selection_probe = report.save_world_selection_context_probe.as_ref();
|
||||
let world_selection_context = selection_probe.map(build_save_world_selection_role_analysis);
|
||||
let world_economic_tuning = report.save_world_economic_tuning_probe.clone();
|
||||
let company_header_probe = report.save_company_collection_header_probe.as_ref();
|
||||
let chairman_header_probe = report
|
||||
.save_chairman_profile_collection_header_probe
|
||||
|
|
@ -2919,6 +2960,13 @@ pub fn inspect_save_company_and_chairman_analysis_bytes(
|
|||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let company_share_prices = company_entries
|
||||
.iter()
|
||||
.filter_map(|entry| {
|
||||
round_f64_to_i64(entry.cached_share_price_f32 as f64)
|
||||
.map(|share_price| (entry.company_id, share_price))
|
||||
})
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
|
||||
let chairman_entries = if let Some(header_probe) = chairman_header_probe {
|
||||
let record_start_offset =
|
||||
|
|
@ -2963,6 +3011,18 @@ pub fn inspect_save_company_and_chairman_analysis_bytes(
|
|||
build_save_qword_candidate(&bytes, record_offset, *relative_offset)
|
||||
})
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
let rounded_current_cash = round_f64_to_i64(current_cash)?;
|
||||
let derived_holdings_share_price_total = derive_chairman_holdings_share_price_total(
|
||||
&holdings_by_company,
|
||||
&company_share_prices,
|
||||
);
|
||||
let derived_net_worth_share_price_total = derived_holdings_share_price_total
|
||||
.and_then(|holdings_total| rounded_current_cash.checked_add(holdings_total));
|
||||
let derived_cached_purchasing_power_total =
|
||||
derive_chairman_cached_purchasing_power_total(
|
||||
rounded_current_cash,
|
||||
&cached_scalar_candidates,
|
||||
);
|
||||
entries.push(SmpSaveChairmanRecordAnalysisEntry {
|
||||
profile_id,
|
||||
name,
|
||||
|
|
@ -2970,6 +3030,9 @@ pub fn inspect_save_company_and_chairman_analysis_bytes(
|
|||
current_cash,
|
||||
linked_company_id,
|
||||
holdings_by_company,
|
||||
derived_holdings_share_price_total,
|
||||
derived_net_worth_share_price_total,
|
||||
derived_cached_purchasing_power_total,
|
||||
cached_scalar_candidates,
|
||||
});
|
||||
}
|
||||
|
|
@ -2985,6 +3048,12 @@ pub fn inspect_save_company_and_chairman_analysis_bytes(
|
|||
.to_string(),
|
||||
);
|
||||
}
|
||||
if world_economic_tuning.is_some() {
|
||||
notes.push(
|
||||
"World analysis now also exports the fixed six-lane economic tuning float block from the same 0x32c8 world payload; current atlas evidence still treats that band as distinct from the issue-0x37 investor-confidence family."
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
if !company_entries.is_empty() {
|
||||
notes.push(
|
||||
"Company debt is derived from the grounded bond table at [company+0x5b/+0x5f] by summing live principal slots.".to_string(),
|
||||
|
|
@ -2995,11 +3064,19 @@ pub fn inspect_save_company_and_chairman_analysis_bytes(
|
|||
notes.push(
|
||||
"Company scalar_dword_candidates expose the current checked-in raw save windows around support/share-price/calendar lanes, and post_capacity_dword_candidates expose the immediate dwords after [company+0x7680] for deeper track-count and record-tail analysis.".to_string(),
|
||||
);
|
||||
notes.push(
|
||||
"Current atlas evidence ties company current_cash and book_value_per_share to stat-family 0x2329 slots 0x0d and 0x1d, so the remaining save-native company finance/governance closure likely needs a structured company-stat family reconstruction instead of more isolated raw offsets."
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
if !chairman_entries.is_empty() {
|
||||
notes.push(
|
||||
"Chairman cached_scalar_candidates expose the adjacent qword band rooted at [profile+0x1e9], now including raw qword hex and signed/f64 views for further purchasing-power analysis.".to_string(),
|
||||
);
|
||||
notes.push(
|
||||
"Chairman analysis now also derives one holdings-at-cached-share-price total from the grounded company cached_share_price lane and one strongest-cached purchasing-power total from the nonnegative qword cache band."
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
Some(SmpSaveCompanyChairmanAnalysisReport {
|
||||
|
|
@ -3012,6 +3089,7 @@ pub fn inspect_save_company_and_chairman_analysis_bytes(
|
|||
selected_chairman_profile_id: selection_probe
|
||||
.map(|probe| probe.selected_chairman_profile_id),
|
||||
world_selection_context,
|
||||
world_economic_tuning,
|
||||
company_entries,
|
||||
chairman_entries,
|
||||
notes,
|
||||
|
|
@ -3405,6 +3483,30 @@ fn build_save_qword_candidate(
|
|||
})
|
||||
}
|
||||
|
||||
fn derive_chairman_holdings_share_price_total(
|
||||
holdings_by_company: &BTreeMap<u32, u32>,
|
||||
company_share_prices: &BTreeMap<u32, i64>,
|
||||
) -> Option<i64> {
|
||||
let mut total = 0i64;
|
||||
for (company_id, units) in holdings_by_company {
|
||||
let share_price = *company_share_prices.get(company_id)?;
|
||||
total = total.checked_add((*units as i64).checked_mul(share_price)?)?;
|
||||
}
|
||||
Some(total)
|
||||
}
|
||||
|
||||
fn derive_chairman_cached_purchasing_power_total(
|
||||
current_cash: i64,
|
||||
cached_scalar_candidates: &[SmpSaveScalarCandidate],
|
||||
) -> Option<i64> {
|
||||
let strongest_cached_total = cached_scalar_candidates
|
||||
.iter()
|
||||
.filter_map(|candidate| round_f64_to_i64(candidate.value_f64))
|
||||
.filter(|value| *value >= 0)
|
||||
.max()?;
|
||||
current_cash.checked_add(strongest_cached_total)
|
||||
}
|
||||
|
||||
fn build_save_world_selection_role_analysis(
|
||||
probe: &SmpSaveWorldSelectionContextProbe,
|
||||
) -> SmpSaveWorldSelectionRoleAnalysis {
|
||||
|
|
@ -3490,8 +3592,17 @@ fn parse_save_chairman_profile_table_probe(
|
|||
bytes,
|
||||
record_offset + SAVE_CHAIRMAN_RECORD_CACHE_1_OFFSET,
|
||||
)?)?;
|
||||
let cached_scalar_candidates = SAVE_CHAIRMAN_RECORD_CACHE_CANDIDATE_OFFSETS
|
||||
.iter()
|
||||
.map(|relative_offset| {
|
||||
build_save_qword_candidate(bytes, record_offset, *relative_offset)
|
||||
})
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
let holdings_value_total = cache_0.max(cache_1).max(0);
|
||||
let net_worth_total = current_cash.saturating_add(holdings_value_total);
|
||||
let purchasing_power_total =
|
||||
derive_chairman_cached_purchasing_power_total(current_cash, &cached_scalar_candidates)
|
||||
.unwrap_or(net_worth_total);
|
||||
let mut company_holdings = BTreeMap::new();
|
||||
for company_id in 1..=company_id_bound {
|
||||
let slot_offset = record_offset
|
||||
|
|
@ -3511,7 +3622,7 @@ fn parse_save_chairman_profile_table_probe(
|
|||
company_holdings,
|
||||
holdings_value_total,
|
||||
net_worth_total,
|
||||
purchasing_power_total: net_worth_total,
|
||||
purchasing_power_total,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -6343,6 +6454,11 @@ fn inspect_bundle_bytes(bytes: &[u8], file_extension_hint: Option<String>) -> Sm
|
|||
file_extension_hint.as_deref(),
|
||||
container_profile.as_ref(),
|
||||
);
|
||||
let save_world_economic_tuning_probe = parse_save_world_economic_tuning_probe(
|
||||
bytes,
|
||||
file_extension_hint.as_deref(),
|
||||
container_profile.as_ref(),
|
||||
);
|
||||
let save_company_collection_header_probe = parse_save_company_collection_header_probe(
|
||||
bytes,
|
||||
file_extension_hint.as_deref(),
|
||||
|
|
@ -6512,6 +6628,7 @@ fn inspect_bundle_bytes(bytes: &[u8], file_extension_hint: Option<String>) -> Sm
|
|||
rt3_105_post_span_bridge_probe,
|
||||
rt3_105_save_bridge_payload_probe,
|
||||
save_world_selection_context_probe,
|
||||
save_world_economic_tuning_probe,
|
||||
save_company_collection_header_probe,
|
||||
save_chairman_profile_collection_header_probe,
|
||||
save_company_roster_probe,
|
||||
|
|
@ -8043,6 +8160,88 @@ fn parse_save_world_selection_context_probe(
|
|||
None
|
||||
}
|
||||
|
||||
fn parse_save_world_economic_tuning_probe(
|
||||
bytes: &[u8],
|
||||
file_extension_hint: Option<&str>,
|
||||
container_profile: Option<&SmpContainerProfile>,
|
||||
) -> Option<SmpSaveWorldEconomicTuningProbe> {
|
||||
if file_extension_hint != Some("gms") {
|
||||
return None;
|
||||
}
|
||||
let profile = container_profile?;
|
||||
let supported = matches!(
|
||||
profile.profile_family.as_str(),
|
||||
"rt3-classic-save-container-v1"
|
||||
| "rt3-105-save-container-v1"
|
||||
| "rt3-105-scenario-save-container-v1"
|
||||
| "rt3-105-alt-save-container-v1"
|
||||
);
|
||||
if !supported {
|
||||
return None;
|
||||
}
|
||||
|
||||
for chunk_tag_offset in find_u32_le_offsets(bytes, RT3_SAVE_WORLD_BLOCK_CHUNK_TAG) {
|
||||
let payload_offset = chunk_tag_offset + 4;
|
||||
let next_chunk_tag_offset = payload_offset.checked_add(RT3_SAVE_WORLD_BLOCK_LEN)?;
|
||||
if read_u32_at(bytes, next_chunk_tag_offset) != Some(RT3_SAVE_WORLD_BLOCK_NEXT_CHUNK_TAG) {
|
||||
continue;
|
||||
}
|
||||
let mirror_lane = build_save_dword_candidate(
|
||||
bytes,
|
||||
payload_offset,
|
||||
"economic_tuning_mirror_lane_0",
|
||||
RT3_SAVE_WORLD_BLOCK_ECONOMIC_TUNING_MIRROR_RELATIVE_OFFSET,
|
||||
)?;
|
||||
let tuning_lanes = RT3_SAVE_WORLD_BLOCK_ECONOMIC_TUNING_PRIMARY_RELATIVE_OFFSETS
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(lane_index, relative_offset)| {
|
||||
build_save_dword_candidate(
|
||||
bytes,
|
||||
payload_offset,
|
||||
&format!("economic_tuning_lane_{lane_index}"),
|
||||
*relative_offset,
|
||||
)
|
||||
})
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
return Some(SmpSaveWorldEconomicTuningProbe {
|
||||
profile_family: profile.profile_family.clone(),
|
||||
source_kind: "save-direct-world-block".to_string(),
|
||||
semantic_family: "scenario-save-world-economic-tuning".to_string(),
|
||||
chunk_tag_offset,
|
||||
payload_offset,
|
||||
payload_len: RT3_SAVE_WORLD_BLOCK_LEN,
|
||||
payload_len_hex: format!("0x{:x}", RT3_SAVE_WORLD_BLOCK_LEN),
|
||||
mirror_lane,
|
||||
tuning_lanes,
|
||||
evidence: vec![
|
||||
format!(
|
||||
"chunk tag 0x32c8 at 0x{chunk_tag_offset:x} matches the fixed [world+0x04] save block"
|
||||
),
|
||||
format!(
|
||||
"next chunk tag 0x32c9 appears at 0x{next_chunk_tag_offset:x}, matching the documented 0x4f2c payload span"
|
||||
),
|
||||
format!(
|
||||
"mirror lane uses payload +0x{:x} ([world+0x0bde])",
|
||||
RT3_SAVE_WORLD_BLOCK_ECONOMIC_TUNING_MIRROR_RELATIVE_OFFSET
|
||||
),
|
||||
format!(
|
||||
"primary tuning lanes use payload offsets {} matching the documented [world+0x0be2..+0x0bf6] float block",
|
||||
RT3_SAVE_WORLD_BLOCK_ECONOMIC_TUNING_PRIMARY_RELATIVE_OFFSETS
|
||||
.iter()
|
||||
.map(|offset| format!("0x{offset:x}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
),
|
||||
"Current atlas evidence keeps this fixed six-float world tuning band separate from the issue-0x37 investor-confidence lane."
|
||||
.to_string(),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn parse_save_company_collection_header_probe(
|
||||
bytes: &[u8],
|
||||
file_extension_hint: Option<&str>,
|
||||
|
|
@ -14567,6 +14766,52 @@ mod tests {
|
|||
assert_eq!(probe.chairman_role_gate_bytes[..4], [2, 1, 0, 2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_save_world_economic_tuning_probe_from_fixed_world_block() {
|
||||
let mut bytes = vec![0u8; 0x8000];
|
||||
let chunk_tag_offset = 0x3ceusize;
|
||||
let payload_offset = chunk_tag_offset + 4;
|
||||
bytes[chunk_tag_offset..chunk_tag_offset + 4]
|
||||
.copy_from_slice(&RT3_SAVE_WORLD_BLOCK_CHUNK_TAG.to_le_bytes());
|
||||
bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_ECONOMIC_TUNING_MIRROR_RELATIVE_OFFSET
|
||||
..payload_offset + RT3_SAVE_WORLD_BLOCK_ECONOMIC_TUNING_MIRROR_RELATIVE_OFFSET + 4]
|
||||
.copy_from_slice(&1.5f32.to_bits().to_le_bytes());
|
||||
for (lane_index, relative_offset) in
|
||||
RT3_SAVE_WORLD_BLOCK_ECONOMIC_TUNING_PRIMARY_RELATIVE_OFFSETS
|
||||
.iter()
|
||||
.copied()
|
||||
.enumerate()
|
||||
{
|
||||
let value = (lane_index as f32) + 10.25f32;
|
||||
bytes[payload_offset + relative_offset..payload_offset + relative_offset + 4]
|
||||
.copy_from_slice(&value.to_bits().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());
|
||||
|
||||
let probe = parse_save_world_economic_tuning_probe(
|
||||
&bytes,
|
||||
Some("gms"),
|
||||
Some(&SmpContainerProfile {
|
||||
profile_family: "rt3-105-save-container-v1".to_string(),
|
||||
profile_evidence: vec![],
|
||||
is_known_profile: true,
|
||||
}),
|
||||
)
|
||||
.expect("world economic tuning probe should parse");
|
||||
|
||||
assert_eq!(probe.chunk_tag_offset, chunk_tag_offset);
|
||||
assert_eq!(probe.payload_offset, payload_offset);
|
||||
assert_eq!(probe.mirror_lane.relative_offset_hex, "0xbda");
|
||||
assert_eq!(probe.mirror_lane.value_f32, 1.5);
|
||||
assert_eq!(probe.tuning_lanes.len(), 6);
|
||||
assert_eq!(probe.tuning_lanes[0].relative_offset_hex, "0xbde");
|
||||
assert_eq!(probe.tuning_lanes[0].value_f32, 10.25);
|
||||
assert_eq!(probe.tuning_lanes[5].relative_offset_hex, "0xbf2");
|
||||
assert_eq!(probe.tuning_lanes[5].value_f32, 15.25);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loads_selection_only_company_and_chairman_context_from_save_world_probe() {
|
||||
let mut report = inspect_smp_bytes(&[]);
|
||||
|
|
@ -14974,13 +15219,14 @@ mod tests {
|
|||
.copy_from_slice(&0x0000520au32.to_le_bytes());
|
||||
bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x0000520bu32.to_le_bytes());
|
||||
|
||||
for (index, (name, linked, cash, cache0, cache1, holdings)) in [
|
||||
for (index, (name, linked, cash, cache0, cache1, cache4, holdings)) in [
|
||||
(
|
||||
"Collis Huntington",
|
||||
1u32,
|
||||
-107644.0f64,
|
||||
252508.0f64,
|
||||
0.0f64,
|
||||
0.0f64,
|
||||
vec![(1u32, 6000u32)],
|
||||
),
|
||||
(
|
||||
|
|
@ -14989,6 +15235,7 @@ mod tests {
|
|||
-382718.0f64,
|
||||
-283562.0f64,
|
||||
822000.0f64,
|
||||
1_392_000.0f64,
|
||||
vec![(2u32, 9000u32)],
|
||||
),
|
||||
]
|
||||
|
|
@ -15014,6 +15261,8 @@ mod tests {
|
|||
bytes[record_offset + SAVE_CHAIRMAN_RECORD_CACHE_1_OFFSET
|
||||
..record_offset + SAVE_CHAIRMAN_RECORD_CACHE_1_OFFSET + 8]
|
||||
.copy_from_slice(&cache1.to_le_bytes());
|
||||
bytes[record_offset + 0x211..record_offset + 0x211 + 8]
|
||||
.copy_from_slice(&cache4.to_le_bytes());
|
||||
for (company_id, units) in holdings {
|
||||
let slot_offset = record_offset
|
||||
+ SAVE_CHAIRMAN_RECORD_HOLDINGS_BASE_OFFSET
|
||||
|
|
@ -15089,9 +15338,11 @@ mod tests {
|
|||
assert_eq!(table.entries[0].company_holdings.get(&1), Some(&6000));
|
||||
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[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);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -15149,6 +15400,55 @@ mod tests {
|
|||
assert_eq!(qword.value_f64, -2458.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derives_chairman_holdings_share_price_total_from_grounded_company_prices() {
|
||||
let holdings_by_company =
|
||||
BTreeMap::from([(2u32, 19_000u32), (4u32, 1_000u32), (6u32, 2_000u32)]);
|
||||
let company_share_prices = BTreeMap::from([(2u32, 66i64), (4u32, 69i64), (6u32, 27i64)]);
|
||||
|
||||
let total =
|
||||
derive_chairman_holdings_share_price_total(&holdings_by_company, &company_share_prices)
|
||||
.expect("derived holdings total should compute");
|
||||
|
||||
assert_eq!(total, 1_377_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derives_chairman_cached_purchasing_power_from_strongest_nonnegative_cache() {
|
||||
let cached_scalar_candidates = vec![
|
||||
SmpSaveScalarCandidate {
|
||||
relative_offset: 0x1e9,
|
||||
relative_offset_hex: "0x1e9".to_string(),
|
||||
raw_u64: (-343_508.0f64).to_bits(),
|
||||
raw_u64_hex: format!("0x{:016x}", (-343_508.0f64).to_bits()),
|
||||
value_i64: round_f64_to_i64(-343_508.0).expect("i64"),
|
||||
value_f64: -343_508.0,
|
||||
},
|
||||
SmpSaveScalarCandidate {
|
||||
relative_offset: 0x201,
|
||||
relative_offset_hex: "0x201".to_string(),
|
||||
raw_u64: 1_386_000.0f64.to_bits(),
|
||||
raw_u64_hex: format!("0x{:016x}", 1_386_000.0f64.to_bits()),
|
||||
value_i64: round_f64_to_i64(1_386_000.0).expect("i64"),
|
||||
value_f64: 1_386_000.0,
|
||||
},
|
||||
SmpSaveScalarCandidate {
|
||||
relative_offset: 0x211,
|
||||
relative_offset_hex: "0x211".to_string(),
|
||||
raw_u64: 1_392_000.0f64.to_bits(),
|
||||
raw_u64_hex: format!("0x{:016x}", 1_392_000.0f64.to_bits()),
|
||||
value_i64: round_f64_to_i64(1_392_000.0).expect("i64"),
|
||||
value_f64: 1_392_000.0,
|
||||
},
|
||||
];
|
||||
|
||||
let total =
|
||||
derive_chairman_cached_purchasing_power_total(-463_436, &cached_scalar_candidates)
|
||||
.expect("derived purchasing power should compute");
|
||||
|
||||
assert_eq!(total, 928_564);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classifies_rt3_105_post_span_bridge_variants() {
|
||||
let base_trailer = SmpRuntimeTrailerBlock {
|
||||
|
|
|
|||
|
|
@ -100,12 +100,15 @@ The highest-value next passes are now:
|
|||
directly from save-side tagged record families, while the fixed save-side `0x32c8` world block
|
||||
still provides selected company/chairman ids plus the grounded campaign-override and
|
||||
chairman-slot / role-gate analysis bytes; the current raw-save frontier is narrower now:
|
||||
company/chairman identity, active flags, links, chairman cash, chairman holdings, company debt,
|
||||
company track-laying capacity, and collection counts are grounded, while broader company
|
||||
company/chairman identity, active flags, links, chairman cash, chairman holdings, chairman
|
||||
purchasing power, company debt, company track-laying capacity, and collection counts are
|
||||
grounded, while broader company
|
||||
finance/governance scalar lanes plus controller-kind reconstruction still remain conservative
|
||||
defaults until their raw offsets are pinned more strongly; the offline analysis command
|
||||
`runtime inspect-save-company-chairman <save.gms>` now dumps those remaining raw record
|
||||
candidates directly from the rehosted parser, including fixed-world chairman slot / role-gate
|
||||
context, the fixed-world six-float economic tuning band, derived holdings-at-share-price and
|
||||
cached purchasing-power totals,
|
||||
context, company dword candidate windows, and richer chairman qword cache views
|
||||
- a checked-in `EventEffects` export now exists at
|
||||
`artifacts/exports/rt3-1.06/event-effects-table.json`, and a checked-in semantic closure layer
|
||||
|
|
|
|||
|
|
@ -64,11 +64,17 @@ Implemented today:
|
|||
chairman slot / role-gate analysis bytes, while the tagged company and chairman/profile
|
||||
collections now provide save-native roster entries and `observed_entry_count`; raw company debt
|
||||
from the bond table and raw company track-laying capacity from the record tail are grounded too,
|
||||
and chairman purchasing power now reuses the strongest nonnegative cached qword total from the
|
||||
`[profile+0x1e9..]` band plus current cash instead of collapsing to plain net worth;
|
||||
the same fixed world payload now exposes the six-float economic tuning band
|
||||
`[world+0x0be2..+0x0bf6]` through save inspection too, but current atlas evidence still keeps
|
||||
that editor-tuning family separate from the company-governance issue lanes;
|
||||
and `runtime inspect-save-company-chairman <save.gms>` now exposes the remaining raw
|
||||
company/chairman scalar candidates directly from the rehosted parser, including fixed-world
|
||||
chairman slot / role-gate context, company dword candidate windows, and richer chairman qword
|
||||
cache views; the remaining raw-save boundary is company-finance/governance scalar depth plus
|
||||
controller-kind closure, not roster absence
|
||||
chairman slot / role-gate context, company dword candidate windows, richer chairman qword
|
||||
cache views, and derived holdings-at-share-price / cached purchasing-power comparisons; the
|
||||
remaining raw-save boundary is company-finance/governance scalar depth plus controller-kind
|
||||
closure, not roster absence
|
||||
- a checked-in `EventEffects` export now exists too at
|
||||
`artifacts/exports/rt3-1.06/event-effects-table.json`, and a checked-in semantic closure layer
|
||||
now exists at `artifacts/exports/rt3-1.06/event-effects-semantic-catalog.json`
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue