From 534c827dca77a23048b7a3b5bbc951af56e03984 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sat, 18 Apr 2026 06:38:21 -0700 Subject: [PATCH] Rehost selected-year gap scalar owner state --- README.md | 2 + crates/rrt-runtime/src/import.rs | 4 ++ crates/rrt-runtime/src/runtime.rs | 115 ++++++++++++++++++++++++++++++ crates/rrt-runtime/src/step.rs | 23 ++++++ crates/rrt-runtime/src/summary.rs | 21 ++++++ docs/README.md | 3 + docs/rehost-queue.md | 18 ++--- docs/runtime-rehost-plan.md | 4 +- 8 files changed, 180 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index eaa560b..8bb22b1 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,8 @@ action label, and the grounded debt/share payload totals used by the shell news Calendar stepping now also starts to use that same seam directly: `StepCount` and `AdvanceTo` invoke the periodic-boundary service automatically on year rollover, so shellless calendar advance can drive the annual finance stack instead of requiring a separate manual service command. +That stepped world-time path now also refreshes the rehosted selected-year gap scalar owner lane +instead of leaving `[world+0x4ca2]` as a frozen load-time residue. Those bankruptcy branches now follow the grounded owner semantics too: they stamp the bankruptcy year and halve live bond principals in place instead of treating bankruptcy as a liquidation path. The same save-native live bond-slot surface now also carries per-slot maturity years all the way diff --git a/crates/rrt-runtime/src/import.rs b/crates/rrt-runtime/src/import.rs index 5e8029e..f2cdfb1 100644 --- a/crates/rrt-runtime/src/import.rs +++ b/crates/rrt-runtime/src/import.rs @@ -324,6 +324,7 @@ pub fn project_save_slice_to_runtime_state_import( }, }; let mut state = state; + state.refresh_derived_world_state(); state.refresh_derived_market_state(); state.validate()?; @@ -459,6 +460,7 @@ pub fn project_save_slice_overlay_to_runtime_state_import( }, }; let mut state = state; + state.refresh_derived_world_state(); state.refresh_derived_market_state(); state.validate()?; @@ -932,6 +934,8 @@ fn project_save_slice_components( .as_ref() .map(|state| state.lane_value_f32_text.clone()) .unwrap_or_default(), + selected_year_gap_scalar_raw_u32: None, + selected_year_gap_scalar_value_f32_text: None, absolute_counter_restore_kind: Some( if save_slice.world_finance_neighborhood_state.is_some() { "save-direct-world-absolute-counter".to_string() diff --git a/crates/rrt-runtime/src/runtime.rs b/crates/rrt-runtime/src/runtime.rs index e1cbf59..925c5b9 100644 --- a/crates/rrt-runtime/src/runtime.rs +++ b/crates/rrt-runtime/src/runtime.rs @@ -1398,6 +1398,10 @@ pub struct RuntimeWorldRestoreState { #[serde(default)] pub economic_tuning_lane_value_f32_text: Vec, #[serde(default)] + pub selected_year_gap_scalar_raw_u32: Option, + #[serde(default)] + pub selected_year_gap_scalar_value_f32_text: Option, + #[serde(default)] pub absolute_counter_restore_kind: Option, #[serde(default)] pub absolute_counter_adjustment_context: Option, @@ -2352,6 +2356,35 @@ impl RuntimeState { .max(profile.net_worth_total); } } + + pub fn refresh_derived_world_state(&mut self) { + let year_word = self + .world_restore + .packed_year_word_raw_u16 + .map(u32::from) + .or_else(|| { + self.world_restore + .current_calendar_tuple_word_raw_u32 + .zip(self.world_restore.current_calendar_tuple_word_2_raw_u32) + .map(|(word_0, word_1)| { + u32::from(runtime_decode_packed_calendar_tuple(word_0, word_1).year_word) + }) + }) + .unwrap_or(self.calendar.year); + if let Some(value) = runtime_world_selected_year_gap_scalar_from_year_word(year_word) { + self.world_restore.selected_year_gap_scalar_raw_u32 = Some(value.to_bits()); + self.world_restore.selected_year_gap_scalar_value_f32_text = + Some(format!("{value:.6}")); + } + } +} + +pub fn runtime_world_selected_year_gap_scalar_from_year_word(year_word: u32) -> Option { + let normalized = (year_word as f64 - 1850.0) / 150.0; + if !normalized.is_finite() { + return None; + } + Some(normalized.clamp(1.0 / 3.0, 1.0) as f32) } pub fn runtime_company_stat_value( @@ -4968,6 +5001,8 @@ mod tests { economic_tuning_mirror_value_f32_text: None, economic_tuning_lane_raw_u32: Vec::new(), economic_tuning_lane_value_f32_text: Vec::new(), + selected_year_gap_scalar_raw_u32: None, + selected_year_gap_scalar_value_f32_text: None, absolute_counter_restore_kind: Some( "mode-adjusted-selected-year-lane".to_string(), ), @@ -7954,6 +7989,86 @@ mod tests { ); } + #[test] + fn derives_selected_year_gap_scalar_from_year_word() { + assert_eq!( + runtime_world_selected_year_gap_scalar_from_year_word(1830), + Some((1.0f32 / 3.0).clamp(1.0 / 3.0, 1.0)) + ); + assert_eq!( + runtime_world_selected_year_gap_scalar_from_year_word(1900), + Some((50.0f32 / 150.0).clamp(1.0 / 3.0, 1.0)) + ); + assert_eq!( + runtime_world_selected_year_gap_scalar_from_year_word(2000), + Some(1.0) + ); + } + + #[test] + fn refreshes_selected_year_gap_scalar_from_world_restore_calendar() { + let mut state = RuntimeState { + calendar: CalendarPoint { + year: 1830, + month_slot: 0, + phase_slot: 0, + tick_slot: 0, + }, + world_flags: BTreeMap::new(), + save_profile: RuntimeSaveProfileState::default(), + world_restore: RuntimeWorldRestoreState { + packed_year_word_raw_u16: Some(1900), + ..RuntimeWorldRestoreState::default() + }, + metadata: BTreeMap::new(), + companies: Vec::new(), + selected_company_id: None, + players: Vec::new(), + selected_player_id: None, + chairman_profiles: Vec::new(), + 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::default(), + }; + + state.refresh_derived_world_state(); + + assert_eq!( + state.world_restore.selected_year_gap_scalar_raw_u32, + Some(((50.0f32 / 150.0).clamp(1.0 / 3.0, 1.0)).to_bits()) + ); + assert_eq!( + state + .world_restore + .selected_year_gap_scalar_value_f32_text + .as_deref(), + Some("0.333333") + ); + } + #[test] fn derives_company_unassigned_share_pool_from_market_state_and_holdings() { let state = RuntimeState { diff --git a/crates/rrt-runtime/src/step.rs b/crates/rrt-runtime/src/step.rs index 7eba9e1..c4a4bd1 100644 --- a/crates/rrt-runtime/src/step.rs +++ b/crates/rrt-runtime/src/step.rs @@ -140,6 +140,7 @@ pub fn execute_step_command( 0 } }; + state.refresh_derived_world_state(); state.refresh_derived_market_state(); let final_summary = RuntimeSummary::from_state(state); @@ -2968,6 +2969,17 @@ mod tests { state.world_restore.absolute_counter_mirror_raw_u32, Some(885_427_240) ); + assert_eq!( + state.world_restore.selected_year_gap_scalar_raw_u32, + Some((1.0f32 / 3.0).to_bits()) + ); + assert_eq!( + state + .world_restore + .selected_year_gap_scalar_value_f32_text + .as_deref(), + Some("0.333333") + ); } #[test] @@ -3038,6 +3050,17 @@ mod tests { state.world_restore.absolute_counter_mirror_raw_u32, Some(885_911_040) ); + assert_eq!( + state.world_restore.selected_year_gap_scalar_raw_u32, + Some((1.0f32 / 3.0).to_bits()) + ); + assert_eq!( + state + .world_restore + .selected_year_gap_scalar_value_f32_text + .as_deref(), + Some("0.333333") + ); assert!( result .service_events diff --git a/crates/rrt-runtime/src/summary.rs b/crates/rrt-runtime/src/summary.rs index 550b1bf..bab58f2 100644 --- a/crates/rrt-runtime/src/summary.rs +++ b/crates/rrt-runtime/src/summary.rs @@ -70,6 +70,8 @@ pub struct RuntimeSummary { pub world_restore_economic_tuning_mirror_value_f32_text: Option, pub world_restore_economic_tuning_lane_count: usize, pub world_restore_economic_tuning_lane_value_f32_text: Vec, + pub world_restore_selected_year_gap_scalar_raw_u32: Option, + pub world_restore_selected_year_gap_scalar_value_f32_text: Option, pub world_restore_absolute_counter_restore_kind: Option, pub world_restore_absolute_counter_adjustment_context: Option, pub metadata_count: usize, @@ -428,6 +430,13 @@ impl RuntimeSummary { .world_restore .economic_tuning_lane_value_f32_text .clone(), + world_restore_selected_year_gap_scalar_raw_u32: state + .world_restore + .selected_year_gap_scalar_raw_u32, + world_restore_selected_year_gap_scalar_value_f32_text: state + .world_restore + .selected_year_gap_scalar_value_f32_text + .clone(), world_restore_absolute_counter_restore_kind: state .world_restore .absolute_counter_restore_kind @@ -1747,6 +1756,8 @@ mod tests { "0.01".to_string(), "0.01".to_string(), ], + selected_year_gap_scalar_raw_u32: Some(0x3eaaaaab), + selected_year_gap_scalar_value_f32_text: Some("0.333333".to_string()), ..RuntimeWorldRestoreState::default() }, metadata: BTreeMap::new(), @@ -1862,6 +1873,16 @@ mod tests { summary.world_restore_economic_tuning_lane_value_f32_text, vec!["0.75", "0.007", "0.008", "0.009", "0.01", "0.01"] ); + assert_eq!( + summary.world_restore_selected_year_gap_scalar_raw_u32, + Some(0x3eaaaaab) + ); + assert_eq!( + summary + .world_restore_selected_year_gap_scalar_value_f32_text + .as_deref(), + Some("0.333333") + ); } #[test] diff --git a/docs/README.md b/docs/README.md index b1f1b8c..ef3d10a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -166,6 +166,9 @@ The highest-value next passes are now: - shellless calendar advance now also drives that annual seam directly: `StepCount` and `AdvanceTo` invoke periodic-boundary service automatically on year rollover instead of requiring a second manual service pass to make the annual finance stack run +- that stepped world-time path now also refreshes the derived selected-year gap scalar owner lane + `[world+0x4ca2]`, so later selected-year and periodic-boundary world work can build on runtime + state instead of a frozen load-time scalar - 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 diff --git a/docs/rehost-queue.md b/docs/rehost-queue.md index 18a3551..5d52043 100644 --- a/docs/rehost-queue.md +++ b/docs/rehost-queue.md @@ -9,15 +9,13 @@ Working rule: ## Next -- Rehost world time owner-state progression from `CalendarPoint` into `world_restore`: - sync `packed_year_word_raw_u16`, `partial_year_progress_raw_u8`, packed calendar tuple words, - and absolute counter so shellless stepping advances the same finance/economy reader inputs used - by periodic service. -- Make automatic year-rollover periodic service run against a consistent end-of-year world-time - snapshot, then refresh into the new-year snapshot after service commits. -- Rehost the next shellless periodic-boundary world work under - `simulation_service_periodic_boundary_work`, starting with any bounded non-dialog queue/state - family that can execute without shell ownership. +- Rehost the next selected-year periodic-boundary world seam under + `simulation_service_periodic_boundary_work`, starting with the save-world economic tuning mirror + `[world+0x0bde]` and any directly adjacent selected-year companion bands that can be refreshed + from stepped world time without shell ownership. +- Expand the selected-year world-owner surface beyond the new stepped calendar and gap-scalar + lanes when the owning reader/rebuild family is grounded strongly enough to avoid one-off leaf + guesses. ## In Progress @@ -43,6 +41,8 @@ Working rule: ## Recently Done +- Stepped calendar progression now also refreshes save-world owner time fields, including packed + year, packed tuple words, absolute counter, and the derived selected-year gap scalar. - Automatic year-rollover calendar stepping now invokes periodic-boundary service. - Company cash, confiscation, and major governance effects now write through owner state instead of drifting from market/cache readers. diff --git a/docs/runtime-rehost-plan.md b/docs/runtime-rehost-plan.md index d10795c..5e1830d 100644 --- a/docs/runtime-rehost-plan.md +++ b/docs/runtime-rehost-plan.md @@ -215,7 +215,9 @@ flows through save-slice/runtime restore state, so later credit / prime / manage 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 +recent-history weighting path. Stepped world time now also refreshes the derived selected-year gap +scalar owner lane `[world+0x4ca2]`, so later selected-year periodic-boundary world work can build +on runtime state instead of a frozen load-time scalar. The same owned company annual-finance state now also drives a shared company market reader seam for stock-capital, salary, bonus, and the full two-word current/prior issue-calendar tuples, which is a better base for shellless finance simulation than summary-only helpers. That same owned annual-finance state now also derives elapsed