Extend atlas seam annotations across load/save and transport

This commit is contained in:
Jan Petykiewicz 2026-04-13 23:35:17 -07:00
commit 7b44279d7e
7 changed files with 1450 additions and 330 deletions

View file

@ -18,7 +18,24 @@ The broader map-editor page owner is now bounded through
the paired overall growth selector whose effects later appear in the city-growth side of the
simulation. `map_editor_city_region_panel_construct` and
`map_editor_city_region_panel_handle_message` own the city-or-region editing lane with rename and
copy-industry-data flows. That copy side is tighter now too: the handler first enters
copy-industry-data flows. The adjacent economic tuning page is explicit now too:
`map_editor_economic_cost_slider_panel_construct` `0x004cadf0` registers
`map_editor_economic_cost_slider_dispatch` `0x004ca980` on six visible slider controls
`0x5bcd..0x5bd7`, re-publishes the current values from
`[world+0x0be2/+0x0be6/+0x0bea/+0x0bee/+0x0bf2/+0x0bf6]`, and pairs that band with the localized
captions `Prime Rate`, `Merger Premium`, and the construction/maintenance cost family
`Build Stations Cost` through `Steam Engine Cost`. The dispatch side writes those six floats
directly back into the same state band and mirrors the first lane into `[world+0x0bde]`, which
now ties the editor page directly to the `.smp` save/load plateau and the post-load/runtime
consumers grounded elsewhere in the atlas. The read-side presentation seam is explicit now too:
`map_editor_economic_cost_panel_refresh_preview_curve_and_numeric_rows` `0x004caaf0` is the
control-`0x5be1` sibling for that same page. It reads the live six-float band, builds one
preview curve from leading lane `[world+0x0be2]` plus the stepped multiplier table
`[world+0x0be6/+0x0bea/+0x0bee/+0x0bf2/+0x0bf6]`, and republishes the six formatted numeric
rows through the neighboring dynamic-text controls `0x5bce/0x5bd0/0x5bd2/0x5bd4/0x5bd6/0x5bd8`.
That closes the economic tuning page as one real save/load-facing editor family rather than only
a constructor plus one write-only slider dispatcher. The city-or-region copy side is tighter now too: the
handler first enters
`0x00420e00`, which rebuilds the selected region's profile collection `[region+0x37f]`, clones
the source region's live label-weight entries when a source region is present, and otherwise
reseeds a fixed default weight set through repeated `0x004206b0` calls. The no-source companion
@ -49,6 +66,13 @@ The broader map-editor page owner is now bounded through
region id `[region+0x23a]` by squared XY distance through `0x0051db80`, and returns `0` when no
qualifying region is found; `0x004220b0` is the small wrapper above it that returns the matched
region name `[region+0x356]` or the fallback localized scratch string at `0x0062ba90`.
The adjacent scenario-rule page is tighter on the same save/load axis too:
`map_editor_scenario_special_conditions_panel_clear_visible_row_band` `0x004cb1a0` and
`map_editor_scenario_special_conditions_panel_format_row_by_index` `0x004cb1c0` are now the
concrete range and row callbacks behind list control `0xa7fa`, so the persisted 49-dword
scenario-rule band `[world+0x4a7f..+0x4b3f]` is no longer only constructor-shaped in the atlas.
The constructor `0x004cb2b0` binds those callbacks, counts enabled rows directly from that same
dword band, and keeps the trailing scalar `[world+0x4b43]` in the same page family.
`map_editor_territory_panel_construct` and
`map_editor_territory_panel_handle_message` own the territory rename and border-remap lane;
`map_editor_locomotive_availability_panel_construct` plus
@ -69,26 +93,40 @@ The broader map-editor page owner is now bounded through
result is now a full chain rather than only the importer:
`scenario_state_rebuild_port_warehouse_cargo_recipe_runtime_tables` first imports those same five
lines into one repeated array of identical `0xbc`-byte runtime descriptors with no row-index
special casing, and the candidate-side rebuild pass at
special casing, while mode `3` keeps reseeding the primary token lane until it differs from the
one-row subordinate lane and then writes literal `1.0` into the subordinate amount slot
`[desc+0x48]`. The candidate-side rebuild pass at
`structure_candidate_collection_rebuild_runtime_records_from_scenario_state` `0x00412d70` then
projects those descriptors into the live structure collection at `0x0062b268` in two
availability-bank passes. The current local disassembly now shows that this bridge reuses live
candidate slots by prior ordinal field `[candidate+0x794]` when possible, otherwise allocates one
fresh candidate, clones from the current bank's template source candidate, and only then copies in
the imported recipe-line count, shared production-cap float, and packed `0xbc` descriptor strip
from scenario state. Each rebuilt candidate then feeds
from scenario state. The label side is narrower than the earlier note too: current local
disassembly of `0x00412d70` does not consult the recipe-book name at `[state+0x0fe8]` at all.
Instead it formats display stem `[candidate+0x04]` directly from one of two fixed built-in roots
chosen by availability bit `[candidate+0xba] & 1`, together with the current ordinal. Each
rebuilt candidate then feeds
`structure_candidate_rebuild_cargo_membership_and_scaled_rate_tables` `0x00411ee0`, which rebuilds
the per-cargo runtime summary tables. That helper now reads more concretely too: it clears both
emitted cargo-id tables at `[candidate+0x79c]` and `[candidate+0x7a0]`, rebuilds one local
`0x35`-cargo mark band from the descriptor strip, accumulates the scaled per-cargo runtime rates
into `[candidate+0xa1..+0xb8]`, and then allocates the two final compact membership tables from
the `mark>=1` and `mark>=2` sets with counts stored at `[candidate+0x7a4]` and `[candidate+0x7a8]`.
The same scan also makes the production-mode split explicit on the runtime side: descriptor mode
`0` uses the shared production cap at `[candidate+0x2a]` divided by the descriptor amount, while
nonzero mode bypasses that scaling path. The immediate sibling
emitted cargo-id tables at `[candidate+0x79c]` and `[candidate+0x7a0]`, clears the per-cargo
float band at `[candidate+0xa1..+0xb8]`, and fills one local `0x35`-cargo status scratch from
the descriptor strip. Each subordinate row contributes status `1` when the cargo is only
referenced and status `2` when the row is currently year-active, with the helper keeping the
maximum status per remapped cargo id through `0x0062ba8c+0x9a`. It then allocates the two final
compact membership tables from the `status>=1` and `status>=2` sets with counts stored at
`[candidate+0x7a4]` and `[candidate+0x7a8]`. The same scan also makes the production-mode split
explicit on the runtime side: descriptor mode `0` uses the shared production cap at
`[candidate+0x2a]` divided by the descriptor amount, while nonzero mode bypasses that scaling
path and publishes the row amount directly. At that point the candidate import strip is
structurally closed in current local evidence: the local import and rebuild seam is no longer
where the loader reconstructs runtime descriptor or membership tables, but only what the
unresolved supply-marker token family means before those already-grounded candidates are projected
back into live cargo ids. The immediate sibling
`structure_candidate_refresh_recipe_runtime_mode_flags_0x78c_0x790` `0x00411ce0`, which for
subtype byte `[candidate+0x32] == 2` scans the imported descriptor strip and derives two compact
post-import flags from mode `0` presence and subordinate-row presence. The stream-load side is
post-import flags from mode `0` presence and whether every mode-`0` descriptor keeps at least
one subordinate row. The stream-load side is
tighter in the same way: the constructor-side load owner
`structure_candidate_collection_construct_and_stream_load_runtime_records` `0x004131f0`
seeds global pool `0x0062b268` and immediately re-enters the broader collection importer
@ -99,14 +137,39 @@ The broader map-editor page owner is now bounded through
`0x00412ab0`, the collection aggregate subtotal pass over `[pool+0x8c..+0x9c]`, rebuilds the
fixed name catalog at `0x0061dbc2/0x0061dc09` for non-subtype-`1` zero-availability candidates
with live ids `<= 0x6e` via localized string `0x0b53`, and only then re-enters the same
named-availability refresh at `0x00412c10` before returning. The neighboring placed-structure
named-availability refresh at `0x00412c10` before returning. The broader scenario-state reset
side is explicit now too: `0x00436d10` is the shared reset-and-rebuild owner beneath startup and
world-entry paths. It zeroes the late scenario-state bands, seeds the fixed year defaults and
chairman-slot rows, rebuilds the two named availability collections at `[state+0x66b2]` and
`[state+0x66b6]`, re-seeds the twelve recipe books at `[state+0x0fe7]` from the fixed template
at `0x005c9f78`, refreshes selected-year state through `0x00409e80`, `0x00433bd0`,
`0x00435603`, and the year-gap scalar helper `0x00434130`, and only then re-enters
`0x00435630`, `0x0041e970`, `0x00412bd0`, and `0x00436af0`. The startup side is tighter now too:
`0x00438890` does not jump straight into one selector-specific file-load branch. It first
re-enters `0x00436d10`, mirrors one shell-managed label list from `0x006d4020+0x429b0` into local
state, allocates the common runtime pools (`0x0062ba8c`, `0x0062b268`, `0x0062b2fc`,
`0x0062ba88`, `0x006ada84`, `0x0062c120`, `0x006cfcbc`, `0x0062be10`, `0x006ceb9c`,
`0x006cea4c`, `0x006acd34`, `0x0062b244`), and pre-seeds named locomotive-availability rows
through `0x004350b0` before the profile selector at `[profile+0x01]` chooses tutorial,
setup-generation, or file-load lanes. The neighboring placed-structure
side is bounded too: global pool `0x0062b26c` comes from
`placed_structure_collection_construct_empty_runtime_pool` `0x00413230`, while the paired tagged
collection owners `0x00413280` and `0x00413440` now own the broader placed-structure stream
load/save path around tags `0x36b1/0x36b2/0x36b3` and the per-entry virtual load/save slots
`+0x40/+0x44`. The adjacent collection pass `0x00412ab0` is tighter in
the same way: it now reads as a pure stem-policy sweep that refreshes candidate dword `[+0xbc]`
from defaults `0x1869f/3/4` plus the fixed override table at `0x005edca8..0x005edd20`. The
from defaults `0x1869f/3/4` plus the fixed override table at `0x005edca8..0x005edd20`, with the
non-default `3/4` branches driven directly by post-import flag `[candidate+0x78c]` and subtype
byte `[candidate+0x32]`. The stream-load side is tighter too: `0x004120b0` now clearly reads the
fixed header fields first, allocates the descriptor strip as `count*0xbc + 1`, zeroes every
loaded `0xbc` descriptor slot before import, resolves primary mode-`0` cargo names from
`+0x08 -> +0x1c` and subordinate row cargo names from `+0x30 -> +0x44`, clamps descriptor start
year `[desc+0x20]` upward to the resolved cargo availability floor `[cargo+0x21]`, and only then
reruns `0x00411ee0` plus `0x00411ce0`. Its version gates are explicit too: bundles before
`0x3ed` omit `[candidate+0x3b..+0xbb]`, bundles before `0x3f2` keep the narrow `0x02`-byte form
of `[candidate+0xc0]`, nonzero `[candidate+0x4b]` forces `[candidate+0x47] = 1.0` and rescales
`[candidate+0x43]` to at least `0x1388`, and the later stem-index resolution first checks two
built-in alias stems before the full fixed row table at `0x005ed338..0x005edca4`. The
current local file-side result is now tighter too: the
grounded recipe-book root at `0x0fe7` is preserved byte-for-byte across the checked map/save
scenario pairs, so the loader can safely treat the twelve `0x4e1`-byte books as preserved
@ -122,16 +185,20 @@ The broader map-editor page owner is now bounded through
to the supply-half runtime branch and bypassing that scaling on the normalized demand half. The
lower gameplay side is tighter now too: `structure_candidate_query_cargo_runtime_summary_channels`
`0x00412650` is the first grounded consumer beneath that rebuild chain, because it lazily rebuilds
four per-cargo summary banks and returns one direct-supply channel, one cap-normalized supply
channel, one demand or input channel, and one scaled production-output subrow channel for a
requested cargo id. The internal indexing is tighter now too: the direct-supply lane indexes from
`[desc+0x1c]`, the demand or input lane uses that same resolved cargo id in the no-subrow branch,
and the scaled production-output lane indexes each subordinate row directly from
`[desc+0x44+row*0x1c]`, with no post-resolution failure guard between the importer writes and the
summary-bank updates. So the strongest current read is narrower than a full cargo-id decode: if
four per-cargo summary banks and returns one direct primary-cargo scalar channel, one cap-share
primary-cargo scalar channel, one nonzero-mode subrow channel, and one zero-mode cap-scaled
subrow channel for a requested cargo id. The internal indexing is tighter now too: the two
primary-cargo banks index from `[desc+0x1c]`, while both subordinate-row banks index each row
directly from `[desc+0x44+row*0x1c]`. The mode split is tighter in the same way: nonzero-mode
descriptors bypass the primary-cargo banks entirely and only feed the nonzero-mode subrow bank,
while mode-`0` descriptors either add their direct scalar to `[this+0x03a]` or, when subrows are
present, add one cap-share scalar to `[this+0x0a4]` and one cap-scaled row contribution to
`[this+0x178]`. There is still no post-resolution failure guard between the importer writes and
the summary-bank updates. So the strongest current read is narrower than a full cargo-id decode: if
the imported low-16 marker rows fail the exact matcher at `0x0041e9f0`, the resulting `0` ids
will still hit the first summary-bank bucket inside `0x00412650`, but the semantic meaning of
cargo id `0` itself remains ungrounded. The sibling helper
cargo id `0` itself remains ungrounded. So the remaining gap here is semantic only, not another
missing import or rebuild owner. The sibling helper
`structure_candidate_supports_or_references_cargo_id`
`0x004129d0` then uses those same banks plus the cached cargo-membership arrays to answer whether
a live candidate materially references a cargo at all. One compare step tighter, the raw line
@ -195,12 +262,13 @@ The broader map-editor page owner is now bounded through
cargo-name resolution unless another upstream transform exists.
The wrapper layer above that query no longer looks like a hiding place for special treatment
either. Local `objdump` now shows `0x00412960` simply summing the two supply-side floats returned
by `0x00412650`, while `0x004129a0` returns the single scaled production-output lane directly;
by `0x00412650`, while `0x004129a0` returns the single zero-mode cap-scaled subrow lane directly;
neither wrapper checks for cargo id `0` after the query returns. The broader world-side
accumulator at `0x0041e7be` is tighter in the same way: it calls `0x00412650`, requires all four
returned channels to be positive before continuing, queries one linked-instance count through
`0x00413940`, scales each of the four channel values by that count, and then writes the finished
quartet into candidate dwords `[candidate+0x8e/+0x92/+0x96/+0x9a]` while stamping
`0x00413940`, scales the direct-primary, cap-share-primary, nonzero-mode-subrow, and
zero-mode-cap-scaled-subrow lanes by that count, and then writes the finished quartet into
candidate dwords `[candidate+0x8e/+0x92/+0x96/+0x9a]` while stamping
`[candidate+0x8a]` from current world field `[0x006cec78+0x15]`. So the current strongest read
remains structural rather than semantic: unresolved marker rows can still propagate through the
first bank bucket, but the first place that meaning matters is a normal positivity-gated
@ -426,17 +494,19 @@ The broader map-editor page owner is now bounded through
when `0x004349a0` reports more than one active profile. After those gates it forwards target mode
`([shell+0x68] == 0)` into
`shell_set_editor_map_mode_and_refresh_detail_panel_world_and_graphics_side_effects` `0x00482e50`.
The adjacent startup-year strip is grounded now too:
`shell_command_raise_startup_selected_year_scalar_by_half_step_and_refresh_calendar` `0x00441cb0`
and `shell_command_lower_startup_selected_year_scalar_by_half_step_and_refresh_calendar`
`0x00441d20` are the paired registered commands beneath localized ids `0x0db5/0x0db6`. Both
The adjacent time-of-day strip is grounded now too:
`shell_command_increment_time_of_day_presentation_scalar_by_half_step_and_refresh_calendar`
`0x00441cb0` and
`shell_command_decrement_time_of_day_presentation_scalar_by_half_step_and_refresh_calendar`
`0x00441d20` are the paired registered commands beneath localized ids `0x0db5/0x0db6`, whose
RT3.lng captions are `Increment Time Of Day` and `Decrement Time Of Day`. Both
require active scenario state plus the same single-profile gate from `0x004349a0`, clear shell
dwords `[0x006cec74+0x233]` and `[+0x124]`, then adjust startup scalar `[0x006cec78+0xbfa]` by
`+0.5` or `-0.5` before re-entering
`world_set_selected_year_and_refresh_calendar_presentation_state` `0x00409e80` with absolute
counter `[0x006cec78+0x15]` and refreshing the live world view through `0x00439a80`. The exact
player-facing command labels are still open, but the static owner boundary is now clear: these
are not generic shell scalars, they are the paired half-step selected-year presentation commands.
counter `[0x006cec78+0x15]` and refreshing the live world view through `0x00439a80`. So the
registration-side identity is now closed: these are the paired half-step time-of-day
presentation commands, not generic shell scalars and not the earlier selected-year read.
The adjacent progress-status side is tighter now too:
`shell_build_percent_status_payload_clamped_0_to_100` `0x00441d90` is the shared save or load
progress payload builder, and
@ -889,7 +959,8 @@ The broader map-editor page owner is now bounded through
`0x00437d70`. That owner increments the shared shell counter at `[0x006d401c+0xc60]`, opens the
localized cheat prompt `2922` `Do I detect a cheater in the house?\n\nEnter code (or <ESC> to
cancel):`, and then scans the fixed 26-entry table at `0x005ee2c8`. The active selector strip is
now grounded: winner/loss strings `3618/3619/3620/3622` route through `0x004367c0`, selector `1`
now grounded: winner/loss strings `3618/3619/3620/3622` route through
`world_set_outcome_mode_and_copy_cheat_win_or_loss_status_text` `0x004367c0`, selector `1`
jumps to the out-of-line reset branch at `0x004d676c` that clears the selected-company stat bands
rooted at `[company+0x0cfb]`, `[company+0x0d7f]`, and `[company+0x1c47]`, selectors `2` and `3`
post deltas into the selected company and selected chairman profile through `0x0042a080` and
@ -958,10 +1029,11 @@ The broader map-editor page owner is now bounded through
refreshes the live company-detail branch in mode `7`, the stock-buy branch in mode `0x0b`, the
mode-`8` timed overlay only when the current subject id matches the caller dword, and the
`Overview.win` side in mode `9`. Its wrapper
`shell_refresh_active_window_followons_and_adjacent_station_or_company_lists` `0x00436170`
`shell_refresh_active_window_followons_and_adjacent_station_or_train_lists` `0x00436170`
forwards through that helper and then adds the station-list refresh through
`shell_station_list_window_refresh_rows_selection_and_status` `0x00506f30` when mode `4` is
active plus the company-list refresh through `0x005158f0(-1)` when mode `1` is active. The
active plus the `TrainList.win` refresh through
`shell_train_list_window_refresh_controls` `0x005158f0(-1)` when mode `1` is active. The
shared world-side follow-on beneath those shell branches is explicit now too:
`world_refresh_collection_side_effects_after_broad_state_change` `0x004361d0` sweeps the live
region, placed-structure, and three adjacent world collections, re-entering their per-record