railroader-physics-optimizer/README.md
Seton Carmichael 5414fd8979 Initial commit - Physics Overhaul v0.1.1
Features:
- ConsistLOD: fast-path Bezier position update for distant cars (PositionAccuracy
  matched, LocationF/R synced, OnPosition fired, culler sphere kept current)
- ConsistFreezer: per-consist auto-freeze replacing stock all-or-nothing optimizer
- PhysicsTimer: stacked ring buffers (render, FixedUpdate, Tick, PosCars) + report
- PhysicsOverlayGUI: draggable stacked-area chart, reference lines always visible,
  ON/OFF toggles with persistence, opacity control
- Settings: JSON persistence, full UMM settings panel with auto-save
- ConsoleCommands: /rpf with freeze/unfreeze/dump/timing/overlay/forceactive/lod
2026-06-16 12:24:56 -04:00

6.1 KiB

Railroader Physics Overhaul

A Unity Mod Manager mod for Railroader that reduces CPU time spent on train physics without sacrificing gameplay.

This mod is HEAVILY experimental. Although it should be fully compatible and shouldnt corrupt your save, any use on your existing saves is your own risk and I accept no liability.

Features

Physics Optimizer (LOD fast-path)

Cars farther than a configurable distance threshold (default 30 m) get a lightweight position update instead of running the full Bezier-curve solver every physics tick. Every few ticks the car resyncs against the full solver to stay numerically correct. The result: far fewer expensive curve evaluations per frame, with limited to no visible difference to gameplay.

Auto Freeze

Consists that are both slow (< 0.3 m/s) and far from the camera (> 200 m) are skipped entirely by the Verlet integration step. If running the wonderful Stock Optimizer mod please disable this feature and beware that there might be instability.

In-Game Profiler Overlay

A movable HUD window shows a stacked line chart of frame time over the last 300 physics ticks (~5 seconds). Layers from the bottom up:

Color Layer
Blue Render frame time (GPU + CPU render work)
Green FixedUpdate total (all physics)
Yellow Tick() only (Verlet integration)
Orange PositionCars (3D position update)

Reference lines at 16.7 ms (60 fps) and 33.3 ms (30 fps) are always on screen regardless of how well the game is running. Toggle the overlay with /rpf overlay or from the UMM settings panel.

Theory of Operation

Why physics is expensive in Railroader

Each physics tick (FixedUpdate), TrainController calls PositionWheelBoundsFront for every car. This method walks the spline to find the 3D position and orientation for each truck — two expensive distance-to-parameter lookups per car. With long consists on curved track, this dominates frame time.

Physics Optimizer detail

The mod patches Car.PositionWheelBoundsFront with a Harmony prefix. When a car is far from the camera and all its couplers are coupled:

  1. Track bounds (WheelBoundsF/R) are still updated every tick — the constraint solver needs them.
  2. Truck positions are computed, but only every N ticks (controlled by ResyncInterval). Between resyncs, the car keeps the body rotation from the previous tick.
  3. PositionAccuracy matches what the full path would use (High when visible, Standard otherwise) so there is no positional jump when the camera enters the threshold.
  4. OnPosition is fired so TrainController.CarDidPosition runs — this keeps the car-culler bounding sphere, spatial hash, and segment cache current.
  5. LocationF/LocationR are updated each tick so the segment cache never goes stale.

Auto Freeze detail

ShouldSkipTick is replaced. The stock implementation skips the entire solver when all cars are at rest; this mod tracks each car individually and skips cars that are far away and very slow, reducing wasteful simulation of parked cars.

Sampling model (the "% of FixedUpdate" number)

The profiler measures time with Stopwatch.GetTimestamp() (nanosecond resolution, no GC pressure). It accumulates over 60-tick windows. "% of FixedUpdate" is how much of the total physics budget Tick() alone consumed that window — a high percentage means Verlet integration dominates; a low percentage means air brakes, coupler forces, or position updates are the bottleneck.

Installation

Requirements

  1. Download the latest release zip.
  2. Open Unity Mod Manager (Ctrl+F10 in game, or the standalone installer).
  3. Drag the zip onto the Mods tab, or click Install Mod and select the zip.
  4. Launch the game.

Manual installation

  1. Unzip the release into <Railroader install folder>\Mods\RailroaderPhysicsOverhaul\.
  2. The folder must contain Info.json and RailroaderPhysicsOverhaul.dll.
  3. Launch the game.

Console Commands

Open the in-game console and type /rpf <subcommand>:

Command Description
/rpf help List all subcommands
/rpf overlay Toggle the profiler HUD
/rpf timing Print the current timing report to the console
/rpf dump Dump the selected consist's state (cars, speed, coupling, segment)
/rpf lod <meters> Set the LOD distance threshold
/rpf lod off Disable the LOD fast-path entirely
/rpf freeze Freeze the selected consist's physics
/rpf unfreeze Unfreeze the selected consist
/rpf forceactive Toggle ForceActive (bypasses the at-rest check for profiling)

Settings

Open UMM (Ctrl+F10), select Physics Overhaul. All settings auto-save on change.

Setting Default Description
Physics Optimizer On Enable/disable the LOD fast-path
Distance Threshold 30 m Cars beyond this distance use the fast path
Resync Interval 4 ticks Full recalculation every N ticks (1 = every tick, no dead-reckoning)
Auto Freeze On Enable/disable the individual-consist freeze
Auto Freeze Distance 200 m Consists beyond this distance are eligible for freezing
Auto Freeze Speed 0.3 m/s Maximum speed for a consist to be considered stopped
Show Overlay On Show/hide the profiler HUD on game load
Overlay Opacity 100% Profiler window background opacity
Blacklist/Whitelist Off Filter specific road numbers in or out of LOD treatment

Building from Source

dotnet build PhysicsOverhaulMod.csproj

The build output goes directly to <Railroader install folder>\Mods\RailroaderPhysicsOverhaul\. The project references game DLLs relative to the source folder, so it must be placed inside the Railroader game directory.

Prerequisites: .NET SDK 6 or later, Railroader installed in the same directory tree.