- Rust 97.8%
- Python 1.8%
- Java 0.3%
| .cargo | ||
| artifacts | ||
| crates | ||
| docs | ||
| fixtures/runtime | ||
| tools | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| README.md | ||
| RT2.LOG | ||
| rt3_auto_load_winedbg.log | ||
| rt3_manual_load_winedbg.log | ||
Analysis and reimplementation of Railroad Tycoon 3
The old executable is at ./rt3_wineprefix/drive_c/rt3/RT3.exe
Our first task is to understand the executable's high-level control loops and subsystem boundaries well enough to choose good rewrite targets. As we go, we document evidence, keep a curated function map, and stand up Rust tooling that can validate artifacts and later host replacement code.
The long-term direction is still a DLL we can inject into the original executable, patching in
individual functions as we build them out. The active implementation milestone is now a headless
runtime rehost layer that can execute deterministic world work, compare normalized state, and grow
subsystem breadth without depending on the shell or presentation path. The current packed-event
frontier is broader real grouped-descriptor coverage on top of the existing save-slice, snapshot,
overlay-import, compact-control, and symbolic company-target workflows. The runtime already carries
selected-company and controller-role context through overlay imports, and real descriptors 2
Company Cash, 13 Deactivate Company, and 16 Company Track Pieces Buildable now parse and
execute through the ordinary runtime path, and descriptors 1 Player Cash and 14
Deactivate Player now join that batch through the same service engine. Synthetic packed records
still exercise the same runtime without a parallel packed executor. The first grounded
chairman-profile runtime slice now exists too: save-slice or overlay-backed chairman/company
context plus the hidden grouped target-subject lane let those same real descriptors 1 and 14
execute on the grounded chairman scope ordinals 0..3 (condition_true, selected, human,
ai), while wider chairman ordinals remain explicit parity. The first grounded
chairman and governance condition batch is broader now: selected-chairman cash / holdings / net
worth / purchasing-power thresholds and company book-value-per-share / investor-confidence /
management-attitude thresholds now import through the normal event-service path, while wider
chairman ordinals remain explicit frontier. Checked-in save-slice
documents can now also carry explicit company rosters and chairman-profile tables, so the current
company-targeted and chairman-targeted descriptor and condition batches can execute from standalone
save-slice fixtures without overlay snapshots when that context is present; raw .gms inspection
still does not reconstruct those company/chairman collections automatically. A generic
company-governance scalar effect surface now exists in runtime too, but real governance descriptor
ids are still deferred until the checked-in effect-table evidence is stronger. 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-threshold company
finance, company track, aggregate territory track, and company-territory track rows can import
through overlay-backed runtime context. Exact named-territory binding now executes, and the runtime
now also carries the minimal event-owned train roster and opaque economic-status lane needed for
real descriptors 8 Economic Status, 9 Confiscate All, and 15 Retire Train to execute
through the same path. Descriptor 3
Territory - Allow All now executes too, reinterpreted as company-to-territory access rights
rather than a territory-owned policy bit. Whole-game ordinary-condition execution now exists too:
special-condition thresholds, candidate-availability thresholds, and economic-status-code
thresholds now gate imported runtime records through the same service path, and that world-side
condition batch now decodes from checked-in metadata instead of fixture-only ids: real
special-condition label ids, real economic-status ids, and the recovered %1 Avail. candidate
template plus candidate-name side strings all lower into the runtime condition model. Checked-in
whole-game descriptor metadata now drives the first real world-side effect batch too:
special-condition and candidate-availability setters import natively, and descriptor 110
Disable Stock Buying and Selling now lowers into the keyed runtime flag
world.disable_stock_buying_and_selling. The recovered whole-game toggle batch is broader now
too: descriptors 111..138, with descriptor 122 Limited Track Building Amount now landing in
the bounded world_restore.limited_track_building_amount scalar and the remaining boolean lanes
lowering into keyed world_flags, cover finance/trading, construction, and governance
restrictions. Explicit the late recovered special-condition toggles now execute 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 condition decode
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 records can gate whole-game effects on
existing world_flags without fixture-authored placeholder ids. The tracked parity save-slice no
longer depends on a raw unsupported_framing placeholder either: its remaining residue is now one
recovered locomotives-page real_packed_v1 record that lands in the explicit
blocked_unmapped_world_descriptor bucket. The next recovered descriptor band is now 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 are now save-native too. Raw .smp inspection/export reconstructs the persisted
[world+0x66b6] locomotive name table and derives a minimal RuntimeState.locomotive_catalog, so
standalone save-slice imports can now lower recovered locomotive availability and locomotive-cost
rows directly into RuntimeState.named_locomotive_availability and
RuntimeState.named_locomotive_cost without needing overlay snapshots when the save carries enough
catalog context. The remaining recovered scalar world families execute too: cargo-production slots
230..240 lower into cargo_production_overrides, and descriptor 453 lowers into
world_restore.territory_access_cost. Whole-game ordinary-condition breadth now aligns with those
same world-scalar runtime surfaces too: named locomotive availability thresholds, named
locomotive cost thresholds, named cargo-production slot thresholds, aggregate cargo-production
thresholds, factory/farm-mine/other cargo-production thresholds, limited-track-building-amount
thresholds, and territory-access-cost thresholds all gate imported runtime records through the
same service path. Explicit
unmapped world-condition and world-descriptor
frontier buckets still remain where current checked-in metadata stops, and
blocked_missing_locomotive_catalog_context is now reserved for intentionally incomplete save-side
catalog context instead of the normal save-slice path. Cargo slot identity and class metadata are
now save-native too: the recipe-book probe lowers into RuntimeState.cargo_catalog, so save-slice
documents can carry slot labels, class tags, and token-stem evidence alongside the executable
cargo_production_overrides surface without introducing a live cargo-economy model. Shell
purchase-flow, Trainbuy refresh,
cached locomotive-rating recomputation, and selected-profile parity remain out of scope. Mixed
supported/unsupported real rows still stay parity-only. The PE32 hook remains useful as capture and
integration tooling, but it is no longer the main execution milestone.
Project Docs
Bootstrap design and workflow documents live in docs/.
docs/README.md: handbook index and target hashesdocs/control-loop-atlas.md: compatibility index for the split atlasdocs/control-loop-atlas/: canonical atlas section filesdocs/setup-workstation.md: toolchain baseline and local setupdocs/re-workflow.md: repeatable reverse-engineering workflowdocs/function-map.md: canonical function-map schema and conventions
The first committed exports for the canonical 1.06 executable live in artifacts/exports/rt3-1.06/.
Rust Workspace
The Rust workspace is split into focused crates:
rrt-model: shared types for addresses, function-map rows, and control-loop conceptsrrt-runtime: headless runtime state, stepping, normalized event service, and persistence-facing runtime typesrrt-fixtures: fixture schemas, loading, normalization, and diff helpers for rehost validationrrt-cli: validation, runtime fixture execution, state-diff tools, and repo-health checksrrt-hook: minimal Windows DLL scaffold for low-risk in-process loading, capture, and later integration experiments under Wine
For the current headless runtime smoke path, use cargo run -p rrt-cli -- runtime summarize-fixture fixtures/runtime/minimal-world-step-smoke.json or one of the broader runtime fixtures under
fixtures/runtime/.
For the current hook smoke test, run tools/run_hook_smoke_test.sh. It builds the PE32 proxy,
copies it into the local RT3 install, launches the game briefly under Wine with
WINEDLLOVERRIDES=dinput8=n,b, and expects rrt_hook_attach.log to appear.