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

103 lines
6.1 KiB
Markdown

# Railroader Physics Overhaul
A [Unity Mod Manager](https://www.nexusmods.com/site/mods/21) mod for [Railroader](https://store.steampowered.com/app/1638770/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
- [Unity Mod Manager](https://www.nexusmods.com/site/mods/21) installed and configured for Railroader
- Railroader (Steam)
### Installing with Unity Mod Manager (recommended)
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.