Departure & Arrival
startMapFunc
(11 states) and joinMapFunc, plus a 300-frame conductor door window.
6.1startMapFunc: the departure machine
Map mode 1 is an 11-state machine (statics: mode @ 0x6d7978, counter @
0x6d7970). Its states, from the decompilation:
| State | What happens | Exits when |
|---|---|---|
| 0 | Setup: guides, camera, car lights; taikou-readiness wait (checkTaikouRead — the opposing-train data must finish streaming, up to 60-frame grace) | next frame |
| 1 | Fade in → OpenStationInt (station interior) → StationAnnounce(1) (departure-side announcement audio) | immediately → 2 |
| 2 | Wait cnt ≥ 0x3d (61 frames) or station-interior ready, then decide key (menu bit 0x20) → CloseStationInt, PcmStop, mapstart_DoorClose() | decide → 3 |
| 3 | Door-close choreography: door melody MytrainPlay(0xa0) fires at SaveStartNowTime − ⌈se/60⌉ seconds; exit at gameStatus+0x314 == SaveStartNowTime; random “yurasi” camera shake (getRand(0x80)) | clock equal → 4 |
| 4 | Handover: setMainMapMode(2) (driving!), checkStartDemo(1), GameConditionBk |= 1 (RUNNING) | — |
| 5/6 | Lecture-mode variant (tutorial routes): signal-go directly, autodrive begins | — |
| 8/9 | Conductor-mode variant: CheckDoorClose gate, time rewind, on_seKankyo | — |
| 10 | Overtake cinematic branch (CheckOverTakeDiaStation) | — |
State 3's clock rewinds time so the timetable stays coherent; the dwell time the train “spent” at the platform is subtracted from the game clock:
/* startMapFunc state 0, conductor branch — the dwell rewind */
SaveStartNowTime = *(int *)(gameStatus + 0x314);
*(int *)(gameStatus + 0x314) =
SaveStartNowTime - pGameData->schedule_ptr_14[station]->dwell_seconds_14;
if (getGameMode() == 3) /* conductor mode 2 */
*(int *)(gameStatus + 0x314) -= 1;
startMapFunc @ 0x1e9968. The
departure-display clock (“Now 14:31:00 / Avr 14:33:30” in the HUD) is exactly this
gameStatus+0x314 vs the scheduled second.6.2Doors: mapstart_DoorClose and CheckDoorClose
mapstart_DoorClose (@ 0x1e98b8, called at state 2→3) is the
departure trigger:
- Clears
GameConditionBk &= ~0x104: lifts the emergency-brake hold and the doors bit; - Calls
changeStartSignalGo(): the 出発進行!! (departure proceed) signal event; - Saves
SaveStartNowTimeand seeds the replay recorder; - Orchestrates the HUD with
inGameSp()(the speedometer/notch/gauge widgets enter).
CheckDoorClose (@ 0x1e619c) is the conductor-mode gate:
it reads the menu key for the door-close command (bits 7/5), gated on PcmCheck()
(the announcement audio must have finished) with a 300-frame (0x12C) timeout
before forcing progress. The ConductDoorFunc map mode (10) reuses the same
300-frame window for its four sub-states.
6.3Arrival: joinMapFunc, coupling to a train ahead
When the diagram ends with the train coupling to another (the Yamanote loop's “join”), map mode 11 runs a dedicated cinematic with hard numeric gates (from the decompilation):
| Check | Threshold | Result |
|---|---|---|
| Approach camera | moveJoinLen ≥ 23.0 m | switch to coupling camera, BgmPlay(0xd55,1) |
| Coupling speed window | 2.0 ≤ Speed ≤ 6.0 km/h | outside → coupling fails (restart approach) |
| Coupling distance window | 2.75–3.26 m | inside → bonus type 41 (“perfect coupling”) |
| Speed bonus | 2–6 km/h at impact | bonus type 39 |
| Impact sound | MytrainPlay(0x3e9) | one-shot coupling impact |
/* joinMapFunc @ 0x1e8a38 — init (param −1) highlights */
GameConditionBk |= 0x1100; // doors + join-game bits
RilMoveRoute(_RunRoutePos, -_RunDir * K); // place train at coupling point
setCommonSpeedLim(0, 5.0); // hard 5 km/h limit for the approach
initJoinTime(gameStatus[0xd3] < 2 ? 0x1e : 0x19); // 30/25 s approach clock
joinMapFunc's “horn at the right moment” bonus (setBonusScore(0x28))
is mechanically unreachable: the moving-branch fallback sets
hornBonusFlg = 1 every frame the train is still moving, and the train moves for
hundreds of frames before the stopped-branch check can ever see the flag at 0 again. Documented
as dead in the binary and preserved here as a reimplementation cautionary tale.6.4The departure deadlock: a reimplementation lesson
Every state machine above assumes its audio events complete. On the
original hardware the announcement, the door melody and the departure whistle are PCM clips
whose completion is observable (PcmCheck(), source-state polling). In an
environment where those callbacks never fire, the interlocks never open; the platform holds
GameCondition = 0x104 forever, gameStatus[0] stays 0, and the physics
force-hold (setNochBrake's 0x204 branch) keeps the train braked. This exact
deadlock was reproduced and diagnosed live while preparing this report:
/* observed live: the stuck platform state */
GameConditionBk = 0x104 // EMERGENCY_BRAKE | DOORS — set by initMapFunc
gameStatus[0] = 0 // "not running" — physics gated
mainMapMode = 1 // startMapFunc, waiting on audio events
_Noch/_Brake = 0/0 // setNochBrake holds brake via bit 0x4
The release path, verified by writing the running process's memory: clear bits
0x304 (emergency + pre-departure + game-over) and set bit 0x1
(RUNNING) in GameConditionBk, set gameStatus[0]=3, hold
_Brake=0, apply a notch; the train departs, the signal event fires
(出発進行!!), and physics integrate normally. A reimplementation should simply ensure its
audio backend always completes clips; the game logic itself is sound.