06 · Gameplay Systems

Departure & Arrival

The choreography that brackets every station visit: doors, announcements, the departure signal, and the coupling sequence. Two state machines, startMapFunc (11 states) and joinMapFunc, plus a 300-frame conductor door window.
Station announcement during departure sequence
The platform sequence: doors open, station announcement plays, passengers board. The banner names the diagram; the signal panel (top-left) shows the cab-signal aspect.
Diagram title card during load
The diagram title card shown while the route's first area packs stream in.
Intro camera arriving at the platform
The intro cinematic's arrival frame: passengers waiting, the train berthing. Unloaded textures render as flat green quads (Chapter 9's streaming seam).

6.1startMapFunc: the departure machine

Map mode 1 is an 11-state machine (statics: mode @ 0x6d7978, counter @ 0x6d7970). Its states, from the decompilation:

StateWhat happensExits when
0Setup: guides, camera, car lights; taikou-readiness wait (checkTaikouRead — the opposing-train data must finish streaming, up to 60-frame grace)next frame
1Fade in → OpenStationInt (station interior) → StationAnnounce(1) (departure-side announcement audio)immediately → 2
2Wait cnt ≥ 0x3d (61 frames) or station-interior ready, then decide key (menu bit 0x20) → CloseStationInt, PcmStop, mapstart_DoorClose()decide → 3
3Door-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
4Handover: setMainMapMode(2) (driving!), checkStartDemo(1), GameConditionBk |= 1 (RUNNING)—
5/6Lecture-mode variant (tutorial routes): signal-go directly, autodrive begins—
8/9Conductor-mode variant: CheckDoorClose gate, time rewind, on_seKankyo—
10Overtake 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;
Exhibit: 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.
startMapFunc state machine
The departure machine. The two audio gates (announcement completion, door melody) are the platform's natural pacing, and the classic emulation pitfall (§6.4).

6.2Doors: mapstart_DoorClose and CheckDoorClose

mapstart_DoorClose (@ 0x1e98b8, called at state 2→3) is the departure trigger:

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):

CheckThresholdResult
Approach cameramoveJoinLen ≥ 23.0 mswitch to coupling camera, BgmPlay(0xd55,1)
Coupling speed window2.0 ≤ Speed ≤ 6.0 km/houtside → coupling fails (restart approach)
Coupling distance window2.75–3.26 minside → bonus type 41 (“perfect coupling”)
Speed bonus2–6 km/h at impactbonus type 39
Impact soundMytrainPlay(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
A verified dead-code find 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.

← 05 · Menu System