Rehost save-world governance issue byte strip

This commit is contained in:
Jan Petykiewicz 2026-04-17 22:14:08 -07:00
commit d99b7eb5c0
9 changed files with 242 additions and 15 deletions

View file

@ -43,7 +43,9 @@ surface also now exposes `runtime inspect-save-company-chairman <save.gms>` for
company/chairman scalar candidates, including fixed-world chairman slot / role-gate context,
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 the grounded issue-`0x37` pair at `[world+0x29/+0x2d]`, one broader
block is now probed for the grounded issue-`0x37` pair at `[world+0x29/+0x2d]`, and the adjacent
raw issue-byte strip `0x37..0x3a` now also flows through save-slice/runtime restore state as
first-class owner data for later credit / prime-rate / management-attitude readers. One broader
fixed-dword finance neighborhood rooted at `[world+0x0d]` that now carries the saved calendar
tuple and absolute-counter owner lanes directly, and the separate
six-float economic tuning band, but current atlas evidence still keeps that editor-facing
@ -68,8 +70,8 @@ counter. The next bundled annual-finance reader seam is now rehosted on top of t
state too, deriving assigned shares, public float, and rounded cached share price from one shared
company market reader instead of scattering more finance helpers across the runtime. A checked-in
The fixed-world finance neighborhood itself is now widened to 17 dwords rooted at `[world+0x0d]`,
so future issue-`0x38/0x39` closure can build on a broader owned restore-state window rather than
another narrow one-off probe; that same owner surface now also carries the saved absolute counter
so later finance closure can build on a broader owned restore-state window rather than another
narrow one-off probe; that same owner surface now also carries the saved absolute counter
as first-class runtime restore state instead of leaving it on “requires shell context” metadata.
The same save-world owner surface now also carries the packed year word and partial-year progress
lane behind the annual-finance recent-history weighting path, so later finance readers can attach

View file

@ -74,6 +74,12 @@ pub struct ExpectedRuntimeSummary {
#[serde(default)]
pub world_restore_issue_37_value: Option<u32>,
#[serde(default)]
pub world_restore_issue_38_value: Option<u32>,
#[serde(default)]
pub world_restore_issue_39_value: Option<u32>,
#[serde(default)]
pub world_restore_issue_3a_value: Option<u32>,
#[serde(default)]
pub world_restore_issue_37_multiplier_raw_u32: Option<u32>,
#[serde(default)]
pub world_restore_issue_37_multiplier_value_f32_text: Option<String>,
@ -545,6 +551,30 @@ impl ExpectedRuntimeSummary {
));
}
}
if let Some(value) = self.world_restore_issue_38_value {
if actual.world_restore_issue_38_value != Some(value) {
mismatches.push(format!(
"world_restore_issue_38_value mismatch: expected {value}, got {:?}",
actual.world_restore_issue_38_value
));
}
}
if let Some(value) = self.world_restore_issue_39_value {
if actual.world_restore_issue_39_value != Some(value) {
mismatches.push(format!(
"world_restore_issue_39_value mismatch: expected {value}, got {:?}",
actual.world_restore_issue_39_value
));
}
}
if let Some(value) = self.world_restore_issue_3a_value {
if actual.world_restore_issue_3a_value != Some(value) {
mismatches.push(format!(
"world_restore_issue_3a_value mismatch: expected {value}, got {:?}",
actual.world_restore_issue_3a_value
));
}
}
if let Some(value) = self.world_restore_issue_37_multiplier_raw_u32 {
if actual.world_restore_issue_37_multiplier_raw_u32 != Some(value) {
mismatches.push(format!(

View file

@ -598,6 +598,30 @@ fn project_save_slice_components(
"save_slice.world_issue_37_value_hex".to_string(),
issue_state.issue_value_hex.clone(),
);
metadata.insert(
"save_slice.world_issue_38_value".to_string(),
issue_state.issue_38_value.to_string(),
);
metadata.insert(
"save_slice.world_issue_38_value_hex".to_string(),
issue_state.issue_38_value_hex.clone(),
);
metadata.insert(
"save_slice.world_issue_39_value".to_string(),
issue_state.issue_39_value.to_string(),
);
metadata.insert(
"save_slice.world_issue_39_value_hex".to_string(),
issue_state.issue_39_value_hex.clone(),
);
metadata.insert(
"save_slice.world_issue_3a_value".to_string(),
issue_state.issue_3a_value.to_string(),
);
metadata.insert(
"save_slice.world_issue_3a_value_hex".to_string(),
issue_state.issue_3a_value_hex.clone(),
);
metadata.insert(
"save_slice.world_issue_37_multiplier_raw_hex".to_string(),
issue_state.multiplier_raw_hex.clone(),
@ -783,6 +807,18 @@ fn project_save_slice_components(
.world_issue_37_state
.as_ref()
.map(|state| state.issue_value),
issue_38_value: save_slice
.world_issue_37_state
.as_ref()
.map(|state| state.issue_38_value),
issue_39_value: save_slice
.world_issue_37_state
.as_ref()
.map(|state| state.issue_39_value),
issue_3a_value: save_slice
.world_issue_37_state
.as_ref()
.map(|state| state.issue_3a_value),
issue_37_multiplier_raw_u32: save_slice
.world_issue_37_state
.as_ref()
@ -5833,6 +5869,12 @@ mod tests {
semantic_family: "world-issue-0x37".to_string(),
issue_value: 3,
issue_value_hex: "0x00000003".to_string(),
issue_38_value: 1,
issue_38_value_hex: "0x01".to_string(),
issue_39_value: 2,
issue_39_value_hex: "0x02".to_string(),
issue_3a_value: 4,
issue_3a_value_hex: "0x04".to_string(),
multiplier_raw_u32: 0x3d75c28f,
multiplier_raw_hex: "0x3d75c28f".to_string(),
multiplier_value_f32_text: "0.060000".to_string(),
@ -6145,6 +6187,9 @@ mod tests {
Some(false)
);
assert_eq!(import.state.world_restore.issue_37_value, Some(3));
assert_eq!(import.state.world_restore.issue_38_value, Some(1));
assert_eq!(import.state.world_restore.issue_39_value, Some(2));
assert_eq!(import.state.world_restore.issue_3a_value, Some(4));
assert_eq!(
import.state.world_restore.issue_37_multiplier_raw_u32,
Some(0x3d75c28f)
@ -6229,6 +6274,14 @@ mod tests {
.map(String::as_str),
Some("3")
);
assert_eq!(
import
.state
.metadata
.get("save_slice.world_issue_39_value")
.map(String::as_str),
Some("2")
);
assert_eq!(
import
.state

View file

@ -45,7 +45,9 @@ pub use pk4::{
};
pub use runtime::{
RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER, RUNTIME_COMPANY_STAT_SLOT_BOOK_VALUE_PER_SHARE,
RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH, RUNTIME_WORLD_ISSUE_INVESTOR_CONFIDENCE,
RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH, RUNTIME_WORLD_ISSUE_CREDIT_MARKET,
RUNTIME_WORLD_ISSUE_INVESTOR_CONFIDENCE, RUNTIME_WORLD_ISSUE_MANAGEMENT_ATTITUDE,
RUNTIME_WORLD_ISSUE_PRIME_RATE,
RuntimeCargoCatalogEntry, RuntimeCargoClass, RuntimeCargoPriceTarget,
RuntimeCargoProductionTarget, RuntimeChairmanMetric, RuntimeChairmanProfile,
RuntimeChairmanTarget, RuntimeCompany, RuntimeCompanyAnnualFinanceState,

View file

@ -407,13 +407,18 @@ pub const RUNTIME_COMPANY_STAT_FAMILY_CONTROL_TRANSFER: u32 = 0x2329;
pub const RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH: u32 = 0x0d;
pub const RUNTIME_COMPANY_STAT_SLOT_BOOK_VALUE_PER_SHARE: u32 = 0x1d;
pub const RUNTIME_WORLD_ISSUE_INVESTOR_CONFIDENCE: u32 = 0x37;
pub const RUNTIME_WORLD_ISSUE_CREDIT_MARKET: u32 = 0x38;
pub const RUNTIME_WORLD_ISSUE_PRIME_RATE: u32 = 0x39;
pub const RUNTIME_WORLD_ISSUE_MANAGEMENT_ATTITUDE: u32 = 0x3a;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RuntimeWorldIssueState {
pub issue_id: u32,
pub raw_value_u32: u32,
pub multiplier_raw_u32: u32,
pub multiplier_value_f32_text: String,
#[serde(default)]
pub multiplier_raw_u32: Option<u32>,
#[serde(default)]
pub multiplier_value_f32_text: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
@ -1027,6 +1032,12 @@ pub struct RuntimeWorldRestoreState {
#[serde(default)]
pub issue_37_value: Option<u32>,
#[serde(default)]
pub issue_38_value: Option<u32>,
#[serde(default)]
pub issue_39_value: Option<u32>,
#[serde(default)]
pub issue_3a_value: Option<u32>,
#[serde(default)]
pub issue_37_multiplier_raw_u32: Option<u32>,
#[serde(default)]
pub issue_37_multiplier_value_f32_text: Option<String>,
@ -1913,11 +1924,29 @@ pub fn runtime_world_issue_state(
RUNTIME_WORLD_ISSUE_INVESTOR_CONFIDENCE => Some(RuntimeWorldIssueState {
issue_id,
raw_value_u32: state.world_restore.issue_37_value?,
multiplier_raw_u32: state.world_restore.issue_37_multiplier_raw_u32?,
multiplier_raw_u32: state.world_restore.issue_37_multiplier_raw_u32,
multiplier_value_f32_text: state
.world_restore
.issue_37_multiplier_value_f32_text
.clone()?,
.clone(),
}),
RUNTIME_WORLD_ISSUE_CREDIT_MARKET => Some(RuntimeWorldIssueState {
issue_id,
raw_value_u32: state.world_restore.issue_38_value?,
multiplier_raw_u32: None,
multiplier_value_f32_text: None,
}),
RUNTIME_WORLD_ISSUE_PRIME_RATE => Some(RuntimeWorldIssueState {
issue_id,
raw_value_u32: state.world_restore.issue_39_value?,
multiplier_raw_u32: None,
multiplier_value_f32_text: None,
}),
RUNTIME_WORLD_ISSUE_MANAGEMENT_ATTITUDE => Some(RuntimeWorldIssueState {
issue_id,
raw_value_u32: state.world_restore.issue_3a_value?,
multiplier_raw_u32: None,
multiplier_value_f32_text: None,
}),
_ => None,
}
@ -2612,6 +2641,9 @@ mod tests {
economic_status_code: None,
territory_access_cost: None,
issue_37_value: None,
issue_38_value: None,
issue_39_value: None,
issue_3a_value: None,
issue_37_multiplier_raw_u32: None,
issue_37_multiplier_value_f32_text: None,
finance_neighborhood_candidates: Vec::new(),
@ -4062,6 +4094,9 @@ mod tests {
save_profile: RuntimeSaveProfileState::default(),
world_restore: RuntimeWorldRestoreState {
issue_37_value: Some(3),
issue_38_value: Some(1),
issue_39_value: Some(2),
issue_3a_value: Some(4),
issue_37_multiplier_raw_u32: Some(0x3d75c28f),
issue_37_multiplier_value_f32_text: Some("0.060000".to_string()),
..RuntimeWorldRestoreState::default()
@ -4104,9 +4139,27 @@ mod tests {
.expect("grounded issue 0x37 state");
assert_eq!(issue.issue_id, RUNTIME_WORLD_ISSUE_INVESTOR_CONFIDENCE);
assert_eq!(issue.raw_value_u32, 3);
assert_eq!(issue.multiplier_raw_u32, 0x3d75c28f);
assert_eq!(issue.multiplier_value_f32_text, "0.060000");
assert_eq!(runtime_world_issue_state(&state, 0x39), None);
assert_eq!(issue.multiplier_raw_u32, Some(0x3d75c28f));
assert_eq!(issue.multiplier_value_f32_text.as_deref(), Some("0.060000"));
assert_eq!(
runtime_world_issue_state(&state, RUNTIME_WORLD_ISSUE_CREDIT_MARKET)
.expect("grounded issue 0x38 state")
.raw_value_u32,
1
);
assert_eq!(
runtime_world_issue_state(&state, RUNTIME_WORLD_ISSUE_PRIME_RATE)
.expect("grounded issue 0x39 state")
.raw_value_u32,
2
);
assert_eq!(
runtime_world_issue_state(&state, RUNTIME_WORLD_ISSUE_MANAGEMENT_ATTITUDE)
.expect("grounded issue 0x3a state")
.raw_value_u32,
4
);
assert_eq!(runtime_world_issue_state(&state, 0x40), None);
assert_eq!(runtime_world_absolute_counter(&state), None);
}

View file

@ -1538,6 +1538,14 @@ pub struct SmpSaveWorldIssue37Probe {
pub payload_offset: usize,
pub payload_len: usize,
pub payload_len_hex: String,
pub issue_37_raw_u8: u8,
pub issue_37_raw_hex: String,
pub issue_38_raw_u8: u8,
pub issue_38_raw_hex: String,
pub issue_39_raw_u8: u8,
pub issue_39_raw_hex: String,
pub issue_3a_raw_u8: u8,
pub issue_3a_raw_hex: String,
pub issue_value_lane: SmpSaveDwordCandidate,
pub multiplier_lane: SmpSaveDwordCandidate,
pub evidence: Vec<String>,
@ -2203,6 +2211,12 @@ pub struct SmpLoadedWorldIssue37State {
pub semantic_family: String,
pub issue_value: u32,
pub issue_value_hex: String,
pub issue_38_value: u32,
pub issue_38_value_hex: String,
pub issue_39_value: u32,
pub issue_39_value_hex: String,
pub issue_3a_value: u32,
pub issue_3a_value_hex: String,
pub multiplier_raw_u32: u32,
pub multiplier_raw_hex: String,
pub multiplier_value_f32_text: String,
@ -3414,6 +3428,12 @@ fn derive_loaded_world_issue_37_state_from_probe(
semantic_family: probe.semantic_family.clone(),
issue_value: probe.issue_value_lane.raw_u32,
issue_value_hex: probe.issue_value_lane.raw_u32_hex.clone(),
issue_38_value: u32::from(probe.issue_38_raw_u8),
issue_38_value_hex: probe.issue_38_raw_hex.clone(),
issue_39_value: u32::from(probe.issue_39_raw_u8),
issue_39_value_hex: probe.issue_39_raw_hex.clone(),
issue_3a_value: u32::from(probe.issue_3a_raw_u8),
issue_3a_value_hex: probe.issue_3a_raw_hex.clone(),
multiplier_raw_u32: probe.multiplier_lane.raw_u32,
multiplier_raw_hex: probe.multiplier_lane.raw_u32_hex.clone(),
multiplier_value_f32_text: format!("{:.6}", probe.multiplier_lane.value_f32),
@ -8798,6 +8818,22 @@ fn parse_save_world_issue_37_probe(
"issue_0x37_value",
RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_VALUE_RELATIVE_OFFSET,
)?;
let issue_37_raw_u8 = read_u8_at(
bytes,
payload_offset + RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_VALUE_RELATIVE_OFFSET,
)?;
let issue_38_raw_u8 = read_u8_at(
bytes,
payload_offset + RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_VALUE_RELATIVE_OFFSET + 1,
)?;
let issue_39_raw_u8 = read_u8_at(
bytes,
payload_offset + RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_VALUE_RELATIVE_OFFSET + 2,
)?;
let issue_3a_raw_u8 = read_u8_at(
bytes,
payload_offset + RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_VALUE_RELATIVE_OFFSET + 3,
)?;
let multiplier_lane = build_save_dword_candidate(
bytes,
payload_offset,
@ -8812,6 +8848,14 @@ fn parse_save_world_issue_37_probe(
payload_offset,
payload_len: RT3_SAVE_WORLD_BLOCK_LEN,
payload_len_hex: format!("0x{:x}", RT3_SAVE_WORLD_BLOCK_LEN),
issue_37_raw_u8,
issue_37_raw_hex: format!("0x{issue_37_raw_u8:02x}"),
issue_38_raw_u8,
issue_38_raw_hex: format!("0x{issue_38_raw_u8:02x}"),
issue_39_raw_u8,
issue_39_raw_hex: format!("0x{issue_39_raw_u8:02x}"),
issue_3a_raw_u8,
issue_3a_raw_hex: format!("0x{issue_3a_raw_u8:02x}"),
issue_value_lane,
multiplier_lane,
evidence: vec![
@ -8829,6 +8873,15 @@ fn parse_save_world_issue_37_probe(
"multiplier lane uses payload +0x{:x} ([world+0x29]); atlas notes tie 0x004339b0 to one companion scalar at that lane before company share-price refresh",
RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_MULTIPLIER_RELATIVE_OFFSET
),
format!(
"the adjacent byte strip at payload +0x{:x}..+0x{:x} carries raw issue slots 0x37..0x3a as {:02x} {:02x} {:02x} {:02x}",
RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_VALUE_RELATIVE_OFFSET,
RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_VALUE_RELATIVE_OFFSET + 3,
issue_37_raw_u8,
issue_38_raw_u8,
issue_39_raw_u8,
issue_3a_raw_u8
),
],
});
}
@ -15638,6 +15691,14 @@ mod tests {
assert_eq!(probe.payload_offset, payload_offset);
assert_eq!(probe.issue_value_lane.relative_offset_hex, "0x29");
assert_eq!(probe.issue_value_lane.value_i32, 3);
assert_eq!(probe.issue_37_raw_u8, 3);
assert_eq!(probe.issue_37_raw_hex, "0x03");
assert_eq!(probe.issue_38_raw_u8, 0);
assert_eq!(probe.issue_38_raw_hex, "0x00");
assert_eq!(probe.issue_39_raw_u8, 0);
assert_eq!(probe.issue_39_raw_hex, "0x00");
assert_eq!(probe.issue_3a_raw_u8, 0);
assert_eq!(probe.issue_3a_raw_hex, "0x00");
assert_eq!(probe.multiplier_lane.relative_offset_hex, "0x25");
assert!((probe.multiplier_lane.value_f32 - 0.06).abs() < f32::EPSILON);
}
@ -15765,6 +15826,14 @@ mod tests {
payload_offset: 0x3d2,
payload_len: RT3_SAVE_WORLD_BLOCK_LEN,
payload_len_hex: format!("0x{:x}", RT3_SAVE_WORLD_BLOCK_LEN),
issue_37_raw_u8: 3,
issue_37_raw_hex: "0x03".to_string(),
issue_38_raw_u8: 1,
issue_38_raw_hex: "0x01".to_string(),
issue_39_raw_u8: 2,
issue_39_raw_hex: "0x02".to_string(),
issue_3a_raw_u8: 4,
issue_3a_raw_hex: "0x04".to_string(),
issue_value_lane: SmpSaveDwordCandidate {
label: "issue_0x37_value".to_string(),
relative_offset: 0x29,
@ -15895,6 +15964,9 @@ mod tests {
.world_issue_37_state
.expect("world issue-0x37 state should load");
assert_eq!(issue_37_state.issue_value, 3);
assert_eq!(issue_37_state.issue_38_value, 1);
assert_eq!(issue_37_state.issue_39_value, 2);
assert_eq!(issue_37_state.issue_3a_value, 4);
assert_eq!(issue_37_state.multiplier_raw_hex, "0x3d75c28f");
assert_eq!(issue_37_state.multiplier_value_f32_text, "0.060000");
let tuning_state = slice

View file

@ -41,6 +41,9 @@ pub struct RuntimeSummary {
pub world_restore_economic_status_code: Option<i32>,
pub world_restore_territory_access_cost: Option<u32>,
pub world_restore_issue_37_value: Option<u32>,
pub world_restore_issue_38_value: Option<u32>,
pub world_restore_issue_39_value: Option<u32>,
pub world_restore_issue_3a_value: Option<u32>,
pub world_restore_issue_37_multiplier_raw_u32: Option<u32>,
pub world_restore_issue_37_multiplier_value_f32_text: Option<String>,
pub world_restore_finance_neighborhood_count: usize,
@ -236,6 +239,9 @@ impl RuntimeSummary {
world_restore_economic_status_code: state.world_restore.economic_status_code,
world_restore_territory_access_cost: state.world_restore.territory_access_cost,
world_restore_issue_37_value: state.world_restore.issue_37_value,
world_restore_issue_38_value: state.world_restore.issue_38_value,
world_restore_issue_39_value: state.world_restore.issue_39_value,
world_restore_issue_3a_value: state.world_restore.issue_3a_value,
world_restore_issue_37_multiplier_raw_u32: state
.world_restore
.issue_37_multiplier_raw_u32,
@ -1177,6 +1183,9 @@ mod tests {
absolute_counter_raw_u32: Some(5),
absolute_counter_mirror_raw_u32: Some(5),
issue_37_value: Some(3),
issue_38_value: Some(1),
issue_39_value: Some(2),
issue_3a_value: Some(4),
issue_37_multiplier_raw_u32: Some(0x3d75c28f),
issue_37_multiplier_value_f32_text: Some("0.06".to_string()),
economic_tuning_mirror_raw_u32: Some(0x3f46dff5),
@ -1256,6 +1265,9 @@ mod tests {
Some(5)
);
assert_eq!(summary.world_restore_issue_37_value, Some(3));
assert_eq!(summary.world_restore_issue_38_value, Some(1));
assert_eq!(summary.world_restore_issue_39_value, Some(2));
assert_eq!(summary.world_restore_issue_3a_value, Some(4));
assert_eq!(
summary.world_restore_issue_37_multiplier_raw_u32,
Some(0x3d75c28f)

View file

@ -118,12 +118,14 @@ The highest-value next passes are now:
owned runtime data instead of one more guessed save offset; the first runtime-side `0x2329`
stat-family reader seam is now also rehosted for slots `0x0d` and `0x1d`, and the saved
stat-band windows themselves now carry 32 dwords per root; the matching world-side issue reader
seam is now rehosted for the grounded `0x37` lane, and selected-company summaries now expose the
seam is now rehosted for the grounded `0x37` lane, and the adjacent raw issue-byte strip
`0x37..0x3a` now also flows through save-slice/runtime restore state for later credit / prime
/ management readers; selected-company summaries now expose the
unassigned share pool derived from outstanding shares minus chairman-held shares for later annual
finance logic; that same owned company market state now also backs a bundled annual-finance
reader seam for assigned shares, public float, and rounded cached share price; the fixed-world
finance neighborhood is now widened to 17 dwords rooted at `[world+0x0d]` so later issue-family
closure can target a broader owned restore-state window, and the saved absolute counter now
readers can target a broader owned restore-state window, and the saved absolute counter now
flows through normal runtime restore state instead of staying on shell-context metadata; that
same world owner surface now also carries the packed year word and partial-year progress lane
behind annual-finance recent-history weighting; the same annual-finance state now also

View file

@ -210,8 +210,9 @@ reader instead of guessing another finance leaf. The same owned company market s
supports a bundled annual-finance reader seam for assigned shares, public float, and rounded
cached share price, which is a better base for later dividend / issue-calendar simulation than
scattered single-field helpers. The fixed-world finance neighborhood is now widened to 17 dwords
rooted at `[world+0x0d]`, so later issue-`0x38/0x39` closure can build on a broader owned
restore-state window instead of another narrow probe; that same owner surface now also carries
rooted at `[world+0x0d]`, and the adjacent raw world issue-byte strip `0x37..0x3a` now also
flows through save-slice/runtime restore state, so later credit / prime / management readers can
build on owned issue state instead of another narrow probe; that same owner surface now also carries
the saved world absolute counter as first-class runtime restore state instead of shell-context
metadata, plus the packed year word and partial-year progress lane that feed the annual-finance
recent-history weighting path. The same owned company annual-finance state