# RT3 Reverse-Engineering Handbook

This handbook is the project bootstrap for reverse-engineering and rewriting Railroad Tycoon 3.
It is written for future us first: enough structure to resume work quickly, without pretending the
project is already mature.

## Canonical Target

- Canonical executable: `rt3_wineprefix/drive_c/rt3/RT3.exe` (patch 1.06)
- Reference executable: `rt3_wineprefix/drive_c/rt3_105/RT3.exe` (patch 1.05)
- Canonical SHA-256: `01b0d2496cddefd80e7e8678930e00b13eb8607dd4960096f527564f02af36d4`
- Reference SHA-256: `9e96b0695cb722a700f99c8dce498d34da7235e562b1e275bcc1764f8c9b7eb1`

## Documents

- `setup-workstation.md`: toolchain baseline and local environment setup.
- `re-workflow.md`: how to analyze the binary, record findings, and export reusable artifacts.
- `function-map.md`: canonical schema and conventions for function-by-function mapping.
- `control-loop-atlas.md`: compatibility index for the split atlas, preserving legacy anchors.
- `control-loop-atlas/`: canonical section files for the atlas narrative.
- `runtime-rehost-plan.md`: bottom-up runtime replacement plan and milestone breakdown.

## Repo Conventions

- `docs/`: stable project guidance and durable design notes.
- `tools/py/`: committed Python helpers for analysis and validation.
- `artifacts/exports/`: committed derived outputs that can be regenerated.
- Local-only state stays untracked: `.venv/`, Ghidra projects, Rizin databases, crash dumps, and other
  bulky/generated working files.

## Current Baseline

The current technical milestone is a repeatable loop-mapping workflow for the 1.06 executable.
Before injection work or deep file-format work, we capture:

- executable hashes and PE metadata
- section layout, imports, and notable strings
- a starter subsystem inventory plus a control-loop atlas
- focused address and string context exports for branch-deepening passes
- a reusable CLI RE kit for branch dossiers where the atlas needs deeper grounding
- a stable curated function ledger in `artifacts/exports/rt3-1.06/function-map.csv`

Current coverage is broad enough to support future sessions without rediscovery, especially in:

- CRT startup and bootstrap handoff
- shell frame, layout, presentation, deferred-message, and frontend overlay flow
- Multiplayer.win UI, chat, session-event, and transport ownership
- map/scenario load and text-export paths
- shared support layers such as intrusive queues, vectors, hashed stores, and tracked heaps

README maintenance rule:

- Keep this section at subsystem level only.
- Do not mirror per-pass function additions here.
- Detailed mapping progress belongs in `artifacts/exports/rt3-1.06/function-map.csv` and the derived branch artifacts under `artifacts/exports/rt3-1.06/`.

Current local tool status:

- Ghidra is installed at `~/software/ghidra`
- `~/software/ghidra/ghidraRun` launches successfully in an interactive shell
- Rizin is installed and available on `PATH`
- `winedbg` works with `rt3_wineprefix`
- RT3 launches under `/opt/wine-stable/bin/wine` when started from `rt3_wineprefix/drive_c/rt3`

## Next Focus

The atlas milestone is broad enough that the next implementation focus has already shifted downward
into runtime rehosting. The current runtime baseline now includes deterministic stepping, periodic
trigger dispatch, normalized runtime effects, staged event-record mutation, fixture execution,
state-diff tooling, tracked save-slice documents for captured-runtime inputs, overlay import
documents that combine captured snapshots with save-derived state, and a packed-event persistence
bridge that now reaches per-record summaries and selective executable import.

The highest-value next passes are now:

- preserve the atlas and function map as the source of subsystem boundaries while continuing to
  avoid shell-first implementation bets
- keep using overlay imports as the context bridge when selectively executable packed rows still
  need runtime context that current save slices and raw save inspection do not yet persist
- treat broader real grouped-descriptor recovery as the active packed-event frontier now that the
  first company-scoped batch already parses, summarizes, and executes through the ordinary runtime
  path when overlay context resolves its symbolic company scope: descriptor `2` `Company Cash`,
  descriptor `13` `Deactivate Company`, and descriptor `16` `Company Track Pieces Buildable`
- descriptors `1` `Player Cash` and `14` `Deactivate Player` now join that executable real batch
  through the same ordinary runtime path, backed by the minimal player runtime and overlay-import
  context
- the first chairman-targeted real grouped rows now execute too through that same path when the
  hidden grouped target-subject lane resolves to grounded chairman scope ordinals `0..3`:
  `condition_true_chairman`, `selected_chairman`, `human_chairmen`, and `ai_chairmen`; wider
  chairman ordinals stay parity-only under `blocked_chairman_target_scope`
- chairman runtime ownership is broader now too: selected-chairman condition rows for chairman
  cash, holdings value, net worth, and purchasing power import through the same service path, and
  the first grounded company governance issue batch now executes too via book-value-per-share,
  investor-confidence, and management-attitude thresholds; wider chairman target ordinals remain
  frontier
- checked-in save-slice documents can now carry explicit company rosters and chairman profile
  tables too, so the current company-targeted and chairman-targeted descriptor/condition batches
  can execute from standalone save-slice fixtures without overlay snapshots when that context is
  present; raw `.gms` inspection/export still does not reconstruct full company/chairman rosters,
  but it now does reconstruct selection-only company/chairman context from the fixed save-side
  `0x32c8` world block, so overlay imports can reuse base rosters while honoring raw save-native
  selected company/chairman ids, and a tracked overlay fixture now pins that selection-only
  override path explicitly; raw `.gms` inspection now also lifts the save-side tagged collection
  header counts for the company and chairman/profile families into `observed_entry_count`, so
  save-slice exports carry header-level roster counts even while per-entry payload remains absent;
  the same fixed block now also exports the grounded campaign override byte plus the raw chairman
  slot selector and role-gate bytes as analysis-only save fields
- a checked-in `EventEffects` export now exists 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`
- recovered descriptor rows now land on explicit semantic frontier buckets such as
  `blocked_shell_owned_descriptor`, `blocked_evidence_blocked_descriptor`, and
  `blocked_variant_or_scope_blocked_descriptor` instead of generic unmapped-descriptor residue
- the first recovered governance descriptor tranche now executes through the generic
  company-governance scalar effect surface: descriptor `56` `Credit Rating` and descriptor `57`
  `Prime Rate`
- adjacent recovered finance/control-transfer descriptors such as `55` `Stock Prices` and `58`
  `Merger Premium` now land on explicit shell-owned descriptor parity instead of generic unmapped
  descriptor residue, with tracked fixtures now pinning finance, scenario-outcome, and
  control-transfer shell rows explicitly
- the recovered whole-game scalar economy/performance strip `59..104` now has a bounded runtime
  landing surface too: representative rows execute into `RuntimeState.world_scalar_overrides`
  through stable normalized keys such as `world.build_stations_cost` and
  `world.track_maintenance_cost`
- the runtime-variable strip `39..54` now executes too through bounded event-owned scalar maps on
  world/company/player/territory state, and the matching ordinary-condition strip now gates
  imported records through those same runtime-owned variable maps; these variables are still
  runtime-owned only in the current model and are not yet reconstructed from raw saves
- the grounded aggregate cargo-economics descriptors now execute too: descriptor `105`
  `All Cargo Prices` and descriptors `177..179` `All Cargo Production` / `All Factory Production`
  / `All Farm/Mine Production` land on bounded event-owned cargo override state, and the grounded
  named cargo-production strip `180..229` now lands on named cargo production overrides too
- the named cargo-price strip `106..176` now lands on named cargo price overrides too; the
  checked-in static selector reconstruction is now explicit because the broader 1.06 CargoTypes
  corpus has `51` names, the Cargo106 `cargoSkin` corpus has `70`, and the rehosted offline
  selector builder now closes the 71-row price strip as `cargoSkin` plus the core `Rock`
  carry-over; a new
  checked-in offline cargo-source report at
  `artifacts/exports/rt3-1.06/economy-cargo-sources.json` now parses both `CargoTypes` and the
  `Cargo106.PK4` `cargoSkin` descriptors through rehosted code, normalizes localized
  `~####Name` tokens into visible names, builds a merged live cargo registry, and now derives
  exact named cargo-production and named cargo-price selectors from the checked-in bindings;
  dedicated CLI inspector commands now expose both grounded selectors directly, while the same
  report still shows the nine excluded CargoTypes-only industrial names that sit outside the
  71-row price strip
- the add-building strip `503..519` is now explicitly classified as recovered shell-owned parity,
  with tracked fixture coverage, instead of generic unresolved descriptor residue
- widen real packed-event executable coverage descriptor by descriptor after identity, target mask,
  and normalized effect semantics are all grounded, not just after row framing is parsed
- the first grounded condition-side unlock now exists for negative-sentinel `raw_condition_id = -1`
  company scopes, and the first ordinary nonnegative condition batch now executes too: numeric
  thresholds for company finance, company track, aggregate territory track, and company-territory
  track
- exact named-territory binding now executes too, while named-territory no-match cases remain the
  explicit binding blocker frontier
- real descriptors `8` `Economic Status`, `9` `Confiscate All`, and `15` `Retire Train` now join
  the executable batch through the same ordinary runtime path, backed by the opaque economic-status
  lane and the minimal event-owned train roster
- descriptor `3` `Territory - Allow All` now executes as company-to-territory access rights through
  the same ordinary runtime path; shell purchase-flow parity remains out of scope, and mixed
  supported/unsupported real rows still stay parity-only
- whole-game ordinary-condition execution now exists too: special-condition thresholds,
  candidate-availability thresholds, and economic-status-code thresholds now gate imported runtime
  records, and the packed-event frontier now reports explicit unmapped world-condition and
  world-descriptor buckets
- that whole-game condition batch is now metadata-driven too: special-condition label ids,
  economic-status, and the generic `%1 Avail.` candidate-availability template plus candidate-name
  side strings all decode through checked-in world-condition metadata instead of fixture-only ids
- the first real whole-game grouped-descriptor batch is now metadata-driven too: checked-in
  descriptor metadata covers special-condition and candidate-availability setters, and descriptor
  `110` `Disable Stock Buying and Selling` now executes too through the checked-in keyed runtime
  flag `world.disable_stock_buying_and_selling`
- that world-toggle path now covers a broader recovered boolean scenario-rule band too:
  descriptors `111..138` now decode through checked-in metadata into either keyed `world_flags`
  or the bounded `world_restore.limited_track_building_amount` scalar for finance/trading,
  construction, and governance restrictions
- the late recovered world-toggle band now executes too where current evidence is equally strong:
  `Use Bio-Accelerator Cars`, `Disable Cargo Economy`, `Disable Train Crashes`,
  `Disable Train Crashes AND Breakdowns`, and `AI Ignore Territories At Startup`
- whole-game ordinary-condition coverage is broader now too: checked-in world-flag condition ids
  can lower into `world_flag_equals` gates for boolean equality/inequality forms, so real packed
  rows can gate whole-game effects on existing `world_flags`
- the tracked parity save-slice now keeps its remaining non-imported residue as structured
  `real_packed_v1` parity records, with the first captured leftover now pinned to the unresolved
  upper locomotives-page availability tail and moved onto explicit descriptor parity
- the next recovered locomotives-page descriptor batch is partially executable too:
  descriptors `454..456` (`All Steam/Diesel/Electric Locos Avail.`) now lower through checked-in
  metadata into keyed `world_flags`, while the wider locomotive availability/cost scalar bands now
  split cleanly between the grounded lower executable prefix and the explicit unresolved tails
- raw `.smp` inspection/export now reconstructs the persisted save-side named locomotive table and
  derives a minimal locomotive catalog from its row order, so save-slice documents can carry both
  `RuntimeState.named_locomotive_availability` and the catalog context needed for descriptor
  lowering
- recovered scalar locomotive availability and locomotive-cost descriptors now import through that
  save-native or embedded `RuntimeState.locomotive_catalog` context into the ordinary
  `named_locomotive_availability` and `named_locomotive_cost` runtime maps
- the grounded executable lower locomotive prefix now extends through save-backed locomotive id
  `61` (`Zephyr`); the unresolved lower tail and upper locomotive bands stay on explicit parity
- cargo-production `230..240` and territory-access-cost `453` now execute too through minimal
  world-side scalar landing surfaces: slot-indexed `cargo_production_overrides` and
  `world_restore.territory_access_cost`
- recipe-book probing now derives a save-native `cargo_catalog` too, so save-slice documents carry
  stable cargo slot labels and token-stem evidence into runtime state without requiring a separate
  cargo simulation layer
- world-scalar ordinary-condition coverage now matches those runtime surfaces too: checked-in
  metadata lowers named locomotive availability, named locomotive cost, named cargo-production
  slot thresholds, aggregate cargo production, factory/farm-mine/other cargo production,
  limited-track-building-amount, and territory-access-cost rows into explicit runtime condition
  gates
- cargo slot classification is now checked in and save-native too, so the remaining cargo frontier
  is broader descriptor/condition breadth rather than classification or save/import plumbing
- the company/chairman frontier has moved too: checked-in save-slice documents can now carry that
  context natively, so the next work on that axis is broader recovery and eventual raw save
  reconstruction rather than overlay-only ownership
- keep in mind that the current local `.gms` corpus still exports with no packed event collection,
  so real descriptor mapping needs to stay plumbing-first until better captures exist
- use `rrt-hook` primarily as optional capture or integration tooling, not as the first execution
  environment
- keep `docs/runtime-rehost-plan.md` current as the runtime baseline and next implementation slice
  change

Regenerate the initial exports with:

```bash
python3 tools/py/collect_pe_artifacts.py \
  rt3_wineprefix/drive_c/rt3/RT3.exe \
  artifacts/exports/rt3-1.06
```

Regenerate the startup-focused Ghidra exports with:

```bash
python3 tools/py/export_startup_map.py \
  rt3_wineprefix/drive_c/rt3/RT3.exe \
  artifacts/exports/rt3-1.06
```

Regenerate the checked-in `EventEffects` table export with:

```bash
python3 tools/py/extract_event_effects.py \
  rt3_wineprefix/drive_c/rt3/RT3.exe \
  rt3_wineprefix/drive_c/rt3/Data/Language/RT3.lng \
  artifacts/exports/rt3-1.06/event-effects-table.json
```

Regenerate the checked-in `EventEffects` semantic catalog with:

```bash
python3 tools/py/build_event_effect_semantic_catalog.py \
  artifacts/exports/rt3-1.06/event-effects-table.json \
  artifacts/exports/rt3-1.06/event-effects-semantic-catalog.json
```

That default export now walks two roots:

- `entry:0x005a313b`
- `bootstrap:0x00484440`

For a focused branch-deepening pass, regenerate the analysis context exports with:

```bash
python3 tools/py/export_analysis_context.py \
  rt3_wineprefix/drive_c/rt3/RT3.exe \
  artifacts/exports/rt3-1.06 \
  --addr 0x00444dd0 \
  --addr 0x00508730 \
  --addr 0x00508880 \
  --string gpdLabelDB \
  --string gpdCityDB \
  --string 2DLabel.imb \
  --string 2DCity.imb \
  --string "Geographic Labels"
```

For the pending-template dispatch-store branch, regenerate the new branch dossier with:

```bash
python3 tools/py/rt3_rekit.py \
  pending-template-store \
  rt3_wineprefix/drive_c/rt3/RT3.exe \
  artifacts/exports/rt3-1.06
```

That dossier is now a targeted follow-up tool, not the default first pass.
