rrt/docs
2026-04-16 10:23:29 -07:00
..
atlas Split input/save/load atlas and update references 2026-04-12 10:12:10 -07:00
control-loop-atlas Close remaining static atlas and export work 2026-04-14 17:52:45 -07:00
control-loop-atlas.md Split input/save/load atlas and update references 2026-04-12 10:12:10 -07:00
debug-load-workflow.md Add headless runtime tooling and Campaign.win analysis 2026-04-10 01:22:47 -07:00
function-map.md Split control-loop atlas into section files 2026-04-11 17:55:16 -07:00
re-workflow.md Split control-loop atlas into section files 2026-04-11 17:55:16 -07:00
README.md Add named locomotive availability runtime surface 2026-04-16 10:23:29 -07:00
runtime-rehost-plan.md Add named locomotive availability runtime surface 2026-04-16 10:23:29 -07:00
setup-workstation.md Build RE baseline and initial Rust workspace 2026-04-02 23:11:15 -07:00
shell-load-graph.md Add hook debug tooling and refine RT3 atlas 2026-04-08 16:31:33 -07:00

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 live company state that save slices do not 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
  • 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 identified as the locomotives-page Unknown Loco Available band and moved onto the explicit blocked_unmapped_world_descriptor frontier
  • 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 remain recovered-but-parity-only until per-locomotive identity is grounded
  • the runtime now also carries the save-owned named locomotive availability table directly: checked-in save-slice documents can populate RuntimeState.named_locomotive_availability, and imported runtime effects can mutate that map through the ordinary event-service path without needing full live locomotive-pool parity
  • 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:

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:

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

That default export now walks two roots:

  • entry:0x005a313b
  • bootstrap:0x00484440

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

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:

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.