No description
  • Rust 97.8%
  • Python 1.8%
  • Java 0.3%
Find a file
2026-04-17 16:36:48 -07:00
.cargo Build RE baseline and initial Rust workspace 2026-04-02 23:11:15 -07:00
artifacts Ground named cargo price event descriptors 2026-04-17 13:41:44 -07:00
crates Probe save-world economic tuning and chairman power 2026-04-17 16:36:48 -07:00
docs Probe save-world economic tuning and chairman power 2026-04-17 16:36:48 -07:00
fixtures/runtime Ground named cargo price event descriptors 2026-04-17 13:41:44 -07:00
tools Probe raw save selection context 2026-04-17 09:41:16 -07:00
.gitignore Build RE baseline and initial Rust workspace 2026-04-02 23:11:15 -07:00
Cargo.lock Add headless runtime tooling and Campaign.win analysis 2026-04-10 01:22:47 -07:00
Cargo.toml Add headless runtime tooling and Campaign.win analysis 2026-04-10 01:22:47 -07:00
README.md Probe save-world economic tuning and chairman power 2026-04-17 16:36:48 -07:00
RT2.LOG Commit runtime loader and atlas updates 2026-04-11 18:12:25 -07:00
rt3_auto_load_winedbg.log Commit runtime loader and atlas updates 2026-04-11 18:12:25 -07:00
rt3_manual_load_winedbg.log Commit runtime loader and atlas updates 2026-04-11 18:12:25 -07:00

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 now reconstructs both collections automatically: the fixed save-side 0x32c8 world block still supplies selected company/chairman ids plus the campaign override byte and chairman slot/role-gate analysis bytes, and the tagged company / chairman-profile direct-record families now populate save-native roster entries for real .gms imports and exports. The current raw-save boundary is narrower now: company/chairman identity, active flags, links, chairman cash, chairman holdings, chairman purchasing power, company debt, and company track-laying capacity are grounded directly from save records, while broader company finance/governance scalars and controller-kind reconstruction still remain conservative defaults until their raw lanes are pinned more strongly. The offline runtime analysis surface also now exposes runtime inspect-save-company-chairman <save.gms> for those remaining raw company/chairman scalar candidates, including fixed-world chairman slot / role-gate context, explicit company dword candidate windows, richer chairman qword cache views, and derived holdings-at-share-price / cached purchasing-power comparisons. The same fixed 0x32c8 world block is now probed for its six-float economic tuning band too, but current atlas evidence still keeps that editor-facing tuning family separate from the governance issue lanes behind investor confidence and prime-rate math. A checked-in EventEffects export now exists too in artifacts/exports/rt3-1.06/event-effects-table.json, and a checked-in semantic closure layer now exists beside it in 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 anonymous descriptor residue. The first recovered governance descriptor tranche now imports through the generic company-governance scalar effect surface: descriptor 56 Credit Rating and descriptor 57 Prime Rate execute from ordinary real packed rows, while adjacent recovered finance/control-transfer descriptors such as 55 Stock Prices and 58 Merger Premium now land on explicit shell-owned parity instead of anonymous unmapped descriptor residue, and tracked shell-owned fixtures now pin 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 descriptors import into RuntimeState.world_scalar_overrides through stable normalized keys such as world.build_stations_cost, world.track_maintenance_cost, world.all_engine_speeds, and world.hotel_revenue. 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 records through those same maps too, without widening save-native reconstruction or adding a second packed executor. The grounded aggregate cargo-economics descriptors now have bounded runtime landing surfaces too: descriptor 105 All Cargo Prices plus descriptors 177..179 All Cargo Production / All Factory Production / All Farm/Mine Production import into event-owned cargo override state, and the grounded named cargo-production strip 180..229 now imports into named cargo production overrides too, and the named cargo-price strip 106..176 now imports into named cargo price overrides as well. The checked-in static selector reconstruction is now explicit: 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 named price strip as cargoSkin plus the core Rock carry-over. The checked-in artifacts/exports/rt3-1.06/economy-cargo-sources.json report parses both CargoTypes and the Cargo106.PK4 cargoSkin descriptors, 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 makes the residual live-registry gap explicit by showing the nine excluded CargoTypes-only industrial names outside the 71-row price strip. The add-building strip 503..519 is now explicitly classified as recovered shell-owned descriptor parity rather than generic unresolved residue. 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 now lands on explicit descriptor parity instead of a generic unmapped 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 the grounded lower 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, and the grounded executable lower prefix now extends through save-backed locomotive id 61 (Zephyr); the unresolved lower tail and upper locomotive bands now stay on explicit parity instead of synthetic execution. 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 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 hashes
  • docs/control-loop-atlas.md: compatibility index for the split atlas
  • docs/control-loop-atlas/: canonical atlas section files
  • docs/setup-workstation.md: toolchain baseline and local setup
  • docs/re-workflow.md: repeatable reverse-engineering workflow
  • docs/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 concepts
  • rrt-runtime: headless runtime state, stepping, normalized event service, and persistence-facing runtime types
  • rrt-fixtures: fixture schemas, loading, normalization, and diff helpers for rehost validation
  • rrt-cli: validation, runtime fixture execution, state-diff tools, and repo-health checks
  • rrt-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.