07 · Gameplay Systems

Train Physics

A 60 Hz fixed-timestep simulation: controller detents become notch/brake levels, traction and four resistance models integrate into speed, and every constant below was recovered from the binary, enough to reproduce the train's motion exactly.
Departure with HUD
Departure under power: notch 5 held, 「出発進行!!」 signal event, needle climbing. The physics that produce this frame are the subject of this chapter.

7.1From controller to notch: setNochBrake

Each frame, before physics, setNochBrake (@ 0x200630) decides what the handle positions are, andand sometimes overrides the player entirely:

GameCondition maskEffect
0x204Emergency stop: Notch=0, Brake=brakeMax, displayed values forced, input skipped
0x008ATC enforcement: notch cut, Brake = min(brakeMax, DbrakeMax−1), hardBrakeFlg=1
0x102Input lock: displayed handle positions frozen
0x004ATS emergency — applied inside getBrakePower (×4 brake force)

Then it dispatches by controller type: 0 = digital pad (__setpadcontrol), 2/3 = physical detent levers (clamp to nochMax/brakeMax), 4 = analog brake quantized to 0–7 levels against the option calibration curve (thresholds 40/100/137/216 at optiondata+0x34/38/3c/40).

__setpadcontrol (@ 0x20030c) is the digital-pad translator. Its key-mask table (H2set, stride 0x1c per controller type) maps held/just-pressed bits to actions. The safety interlock:

/* __setpadcontrol — notch up requires Brake == 0 */
if (Brake == 0) {
    if (playKey & H2set[type].notch_up) {
        if (Notch < nochMax.nochMax) Notch++;      // P1..P5
    } else if (playKey & H2set[type].notch_down) {
        if (Notch != 0) Notch--;
    } else if (justKey & H2set[type].notch_zero) {
        Notch = 0;                                  // quick-return
    }
}
/* brake up: B1..B8, then a just-press at brakeMax-1 jumps to EB */
Exhibit: the notch-up path. You cannot apply power with the brake applied: the interlock that defines the game's feel.

The per-controller-type parameter struct (base 0x6e17ac):

OffsetField
+0x00nochMax (5 for digital)
+0x04brakeMax (9 incl. EB)
+0x08handlelock (combined-handle mode)
+0x0c / +0x10Dnoch / Dbrake — displayed positions (what the HUD shows)
+0x14DbrakeMax — ATC enforcement target
+0x18hardBrakeFlg — one-frame settle after ATC release
Input chain from evdev to physics
The full input chain. Note the two distinct paths: menu keys (mnkey, from the pad bitmap) and drive keys (plkey, derived from the raw controller buffers), which is why menu navigation and driving respond to different inputs.

7.2The 60 Hz step: ttTrainGo

@ 0x1b9960. Entry gates: conductor mode delegates to AutoRun; invalid formation (TrnFormation_ > 29) rejects; PAUSE (bit 1), INHIBIT (bit 4) or clear RUNNING (bit 0) zeroes the position step. Otherwise:

/* ttTrainGo @ 0x1b9960 — one integration step (condensed) */
sVar5 = getEngineNoch();                       // notch, controller-adjusted
sVar6 = getEngineBrake();                      // brake level
fVar15 = RunRegist(Speed, ...);                // rolling resistance
fVar16 = StartRegist(Speed);                   // starting resistance (≤3.5 km/h)
fVar17 = TunnelRegist(...);                    // tunnel resistance
CurveGradeRegist(..., local_48, &Rc, Train_updown);  // grade + curve → local_48[0], Rc

if (sVar6 >= 0) {                              // brake has precedence over motor
    if (sVar6 != 0) motorForce = 0;            // any brake level kills traction
}
else motorForce = getMotorPower(...) * (AcceleUp ? 2 : 1);

fVar18 = getBrakePower(..., sVar6, local_40);  // brake force
fVar20 = motorForce / TrnAllWeight;
if (Direc_ != 0) fVar20 = -fVar20;             // reverse gear

fVar19 = fVar15 + fVar16 + Rc + fVar17;        // total resistance (sign by speed)
fVar17 = (fVar18 / TrnAllWeight) * 0.00054042; // brake deceleration per frame
local_40[0] = Speed + ((fVar20 - local_48[0]) - fVar19) * 0.00054042;

/* brake clamp: crossing zero stops exactly */
if (Speed > 0) fVar16 = max(local_40[0] - fVar17, 0);
else           fVar16 = min(local_40[0] + fVar17, 0);
Speed = fVar16;
_RunMove = Speed * 0.004623 * _RunDir;         // meters per frame (1/216.3)
setGsensor(0);
/* coasting bonus: notch 0 + brake 0 accumulates dist5; every 5 m → setInertiaRunScore */
Exhibit: the integrator. Two magic constants carry the whole model: 0.00054042 (force→Δspeed per frame) and 0.004623 (km/h→m/frame, i.e. 1/216.3; 1 m/s = 3.6 km/h ÷ 60 fps ≈ 0.0167… this is the authored scale, not SI).
Physics step dataflow
One physics frame. Traction and the four resistances integrate speed; brake force is applied with a zero-crossing clamp; the position step derives from speed.

7.3Traction: getMotorPower

Traction is table interpolation, not a formula: per motor model, a 3-row × 36-sample force table (rows = LimCr_ limit class, columns = 10 km/h speed bins 0–350), a secondary 15/16-float force-shaping table, and a max-force clamp.

7.4Braking: air pressure, not a lookup

Braking simulates air-brake pressure dynamics. Two layers:

Inner: BrakeFunc_E (all 30 BrakeJumpTbl rows point here): the target pressure for notch Bn is

target = ((LimCr + 5) / 6) × PressResist[brakeLevel] × BrakePowerBk

with PressResist = 10 floats @ 0x4251a0: [0.0, 0.1857, 0.3429, 0.5, 0.6429, 0.8286, 1.0, 1.1714, 1.3286, 1.6] (B1…B8 + EB). Pressure rises toward the target by record.rate/150 per frame and falls by record.rate/150 × 1.5; the asymmetry is why releasing the brake feels slower than applying it. record.rate comes from brake_capa (22 × 0x10-byte records @ 0x589ed0).

Outer: getBrakePower: multiplies the smoothed pressure by half the axle count, applies weather slip (rain/snow coefficients 0.003–0.011 against a decaying Kassou_time), then emergency multipliers: ×4 when GameCondition bit 2 (ATS emergency), ×2 when EmergencyBrakeMode. The final deceleration = force / TrnAllWeight × 0.00054042 per frame, clamped at zero speed.

Weather is dead code The only writer of Weather_mot is ttTrainInit, which resets it to 0 (dry) at every train init. No code path ever sets rain/snow; the slip branches never execute in this build. A reimplementation can carry the constants but need not wire weather.

7.5The four resistance models

ModelFormula (from decompilation)When
Rolling RunRegist @ 0x1ba3ac[1.32 + 0.0164·v + v²·(((axles−1)·0.00624 + 0.028)/W)] · coeff · weatheralways; v = |Speed|·1.055, coeff = ExtCapa+0x14
Starting StartRegist @ 0x1ba4d0(3.5 − |Speed|) · 1.1428572|Speed| ≤ 3.5 km/h and moving
Tunnel TunnelRegist @ 0x1ba524avg(ExtCapafront+0x18, ExtCaparear+0x18) · 1.7inside a tunnel
Curve/Grade CurveGradeRegist @ 0x1ba58cgrade: Δh/len · 1000 (× 0.1·speed if positive & v<10) · curve: (800/radius) · 0.028 · (35 − |cant|)sampled per powered vehicle per axle

Train mass includes passengers: TrnAllWeight = massfront + massrear + passengers × 0.0007: the loading-state icons on the platform actually change the physics.

7.6Speed limits, ATS and ATC

Three limit sources combine each frame; the effective limit caps the judged speed and drives the HUD's limit panel:

7.7The G-sensor: punishing jerky driving

setGsensor computes a smoothed jerk value from per-frame speed deltas; judgeGSensor (@ 0x1c5ee0) penalizes when the smoothed value crosses a threshold (with a cooldown via overSpdCtrl_base). This is the “drive smoothly” judge; its thresholds and constants are in Chapter 8.

7.8Key physics globals

AddressNameMeaning
0x6d2470Speedtrain speed, km/h (float)
0x6f0450_RunMovemeters per frame (Speed × 0.004623 × dir)
0x6f0454RunDistdistance travelled in section
0x6f0458_RunDir+1 forward / −1 reverse
0x6d1a44 / 0x6d1a3cNotch / Brakepower notch 0–5 / brake 0–9
0x6ec1c4DistStopdistance to stop point (m; − = overrun)
0x6d2e2c / 0x6d2e48comLim / comAtctrack limit / ATC limit (km/h)
0x6d2468TrnFormation_formation index (0–29)
0x425380limSpeed[]track limit table
0x4251a0PressResist[]brake pressure per level
0x589de0BrakeJumpTbl30 brake dispatch rows
0x58a030TrainFormation30 front/rear capability pairs
0x58a0a8ExtCapa30 × 0x1c capability records
0x58ae74 / 0x58a3f0MotorCapa_DC / _AC22 × 0x7a4 / 1 × 0xa84 motor tables
Reimplementation note The full traction/brake/resistance model above is fully closed: every constant and table layout is known, and a clean-room reimplementation of just this chapter (ttTrainGo + the tables) reproduces the train's motion to within float noise. It is the best-tested claim in this report: an independent autodrive model built from these constants completed real routes on stock hardware with zero penalty accumulators.
← 06 · Departure & Arrival