Train Physics
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 mask | Effect |
|---|---|
| 0x204 | Emergency stop: Notch=0, Brake=brakeMax, displayed values forced, input skipped |
| 0x008 | ATC enforcement: notch cut, Brake = min(brakeMax, DbrakeMax−1), hardBrakeFlg=1 |
| 0x102 | Input lock: displayed handle positions frozen |
| 0x004 | ATS 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 */
The per-controller-type parameter struct (base 0x6e17ac):
| Offset | Field |
|---|---|
| +0x00 | nochMax (5 for digital) |
| +0x04 | brakeMax (9 incl. EB) |
| +0x08 | handlelock (combined-handle mode) |
| +0x0c / +0x10 | Dnoch / Dbrake — displayed positions (what the HUD shows) |
| +0x14 | DbrakeMax — ATC enforcement target |
| +0x18 | hardBrakeFlg — one-frame settle after ATC release |
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 */
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.
- DC models (
MotorCapa_DC, 22 records × 0x7a4 bytes): speed bin =floor(|speed|/10)capped at 34; linear interpolation between bins; secondary index(force − 50) × 0.02over 15 floats; clamp at+0x794(e.g. 310.0 for record 0); fallback force at+0x798. - AC models (
MotorCapa_AC, 0xa84-byte records): same binning, secondary index(force − 25) × 0.04over 16 floats, seven buckets selected by six thresholds at+0x918..+0x92c. - Front/rear contributions weighted by
ExtCapa[...+4](traction weight), then the sum × 1.3 (final constant,DAT_001ba218). AcceleUp != 0(the tutorial speed-up item) doubles the motor force.
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_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
| Model | Formula (from decompilation) | When |
|---|---|---|
Rolling RunRegist @ 0x1ba3ac | [1.32 + 0.0164·v + v²·(((axles−1)·0.00624 + 0.028)/W)] · coeff · weather | always; v = |Speed|·1.055, coeff = ExtCapa+0x14 |
Starting StartRegist @ 0x1ba4d0 | (3.5 − |Speed|) · 1.1428572 | |Speed| ≤ 3.5 km/h and moving |
Tunnel TunnelRegist @ 0x1ba524 | avg(ExtCapafront+0x18, ExtCaparear+0x18) · 1.7 | inside a tunnel |
Curve/Grade CurveGradeRegist @ 0x1ba58c | grade: Δ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:
- Track limits —
limSpeed[]@0x425380:[5, 10, 15, …, 125, 25(ATC), 0];speedLimit(@0x6d2e40) indexes it; index 26+ = unlimited.comLim(@0x6d2e2c) is the live value (65 km/h on the Yamanote departure seen in the screenshots). - ATS —
controlAts(@0x1bd220) walks the signal records (0x1c-byteatsSigentries): each signal's aspect propagates backward through pattern tables (next_pat/chu_pat@ compiled tables), the highest active aspect becomes the enforcement target, and passing a red at speed raisessigChk→ emergency brake +subLifePoint(0xc)penalty. - ATC —
controlAtcmaintains theatcSigBufring of upcoming aspects;comAtc(@0x6d2e48) is the current ATC limit (65 in the captures). When the ATC panel is active (都市高速 style sections), crossing it forcesGameCondition bit 3→setNochBrakecuts power and brakes toDbrakeMax−1.
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
| Address | Name | Meaning |
|---|---|---|
| 0x6d2470 | Speed | train speed, km/h (float) |
| 0x6f0450 | _RunMove | meters per frame (Speed × 0.004623 × dir) |
| 0x6f0454 | RunDist | distance travelled in section |
| 0x6f0458 | _RunDir | +1 forward / −1 reverse |
| 0x6d1a44 / 0x6d1a3c | Notch / Brake | power notch 0–5 / brake 0–9 |
| 0x6ec1c4 | DistStop | distance to stop point (m; − = overrun) |
| 0x6d2e2c / 0x6d2e48 | comLim / comAtc | track limit / ATC limit (km/h) |
| 0x6d2468 | TrnFormation_ | formation index (0–29) |
| 0x425380 | limSpeed[] | track limit table |
| 0x4251a0 | PressResist[] | brake pressure per level |
| 0x589de0 | BrakeJumpTbl | 30 brake dispatch rows |
| 0x58a030 | TrainFormation | 30 front/rear capability pairs |
| 0x58a0a8 | ExtCapa | 30 × 0x1c capability records |
| 0x58ae74 / 0x58a3f0 | MotorCapa_DC / _AC | 22 × 0x7a4 / 1 × 0xa84 motor tables |
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.