Skip to content

About

臺灣鐵路模擬遊戲

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

twrailwaysim — 臺灣鐵路人員模擬器

臺灣鐵路運轉主題的無障礙優先人員模擬遊戲。純鍵盤操作,所有狀態變化都有文字回饋。

可駕駛臺鐵與捷運兩個系統:臺鐵為全臺路網與實際時刻表,捷運為臺北捷運七條線、 新北捷運三鶯線與桃園機場捷運,另有 ATO 與自動駕駛。開場先選要開哪一個。

目前完成範圍:第一階段司機員模式(里程碑 M0–M3,另含 M4/M5 的部分基礎)。 其餘角色與系統尚未實作,詳見〈完成度〉一節。

依據文件:

兩份文件衝突時,以 chat.md 的結論為準。


快速開始

需要 Python 3.13 以上(開發環境為 3.14)。

python -m pip install -e ".[dev,gui]"

啟動遊戲(主控台介面,預設):

python -m railway_sim

一開始會先問要開臺鐵還是捷運,兩個介面都一樣。要跳過這一步就直接指定:

python -m railway_sim --system mrt

只做資料與鍵位檢查,不啟動遊戲:

python -m railway_sim --check

列出可用情境:

python -m railway_sim --list-scenarios

列出可駕駛的車次(可加關鍵字過濾車次、車種、車輛型式、起訖站或沿途車站):

python -m railway_sim --list-services 集集

執行測試:

python -m pytest

情境

情境使用的都是 115 年 7 月 1 日實施時刻表中的實際車次。

代碼 內容
local 區間車 2115 次,豐原往彰化各站停車(含成功站停靠)
express 區間快 2021 次,與 2115 次同路線但通過成功站
tze_chiang 自強號 101 次,臺中往潮州,通過成功站(§25.7)
chengzhui 區間車 2600 次,臺中經成追線往大甲,不經彰化(§10.4)
red_signal 區間車 2115 次+前方列車故障,須在停止號誌前停車
python -m railway_sim --scenario chengzhui

全部 953 個車次都可以直接指定,不限於上列情境:

python -m railway_sim --service 1801

視窗介面沒有命令列可用,因此改由開場的車次選擇視窗提供同樣的能力 (見下一節)。


捷運模式

臺鐵與捷運共用同一個運轉核心、同一組模型與同一份鍵位表,差別只在資料目錄: 臺鐵在 data/、捷運在 data/mrt/。開場先選系統,之後的操作完全一樣。

python -m railway_sim --system mrt
python -m railway_sim --system mrt --list-services 板南線
python -m railway_sim --system mrt --service R1001

收錄的路線

資料來自各線的維基百科條目(車站順序、站距、車輛型式與列車營運模式), 只收已通車的車站——條目裡標為規劃中或還沒編號的車站不會出現。

路線 代號 車站 車輛型式 自動化
文湖線 BR 24 VAL256、INNOVIA APM 256 無人駕駛
淡水信義線 R 27 C301、C381 ATO
新北投支線 R 2 C371(3輛編組) ATO
松山新店線 G 19 C371、C381 ATO
小碧潭支線 G 2 C371(3輛編組) ATO
中和新蘆線 O 26 C371 ATO
板南線 BL 23 C321、C341 ATO
環狀線 Y 14 610型 無人駕駛
三鶯線 LB 12 三鶯線電聯車 無人駕駛
桃園機場捷運 A 22 桃捷1000型(普通車)、2000型(直達車) ATO

車站代碼直接採用維基百科的車站編號(R22、A14a、LB02),廣播音檔的檔名 也用同一組代碼,兩邊對得起來。

營運模式

資料裡的單位是營運模式而不是某一列特定的車,例如淡水信義線的 「淡水-象山全程車」「北投-象山區間車」「北投-大安區間車」。每一種模式的 兩個方向各是一個可駕駛的車次,奇數為去程、偶數為回程。

只有機場捷運分車種:普通車(A1~A22 每站皆停)與直達車 (A1-A3-A8-A12-A13,環北直達車另停 A18、A21)。停靠規則由營運模式反推寫進 車站資料,因此把直達車設成停泰山會被資料驗證擋下來。台北捷運各線都是各站停車。

實際時刻

北市府資料平台公布逐班時刻表的路線,帶有真正的發車時刻——不是班距或首末班 的摘要,而是每一站每一班的時刻:

路線 適用 啟用日 全程車行車時間
板南線 平日 2025-11-10 頂埔→南港展覽館 約 44 分
中和新蘆線 平日 2025-04-01 迴龍→南勢角 約 46 分
松山新店線 平日 2023-08-21 松山→新店 約 35 分
淡水信義線 平日(東延段試運轉) 2026-07-23 淡水→象山 約 51 分

40 種營運模式裡有 24 種對得上來源。文湖線、環狀線、三鶯線與機場捷運沒有逐班 時刻的公開來源,因此保持空白——補一份推估的只會讓人以為那是真的(§2.3)。 行前提要會說清楚是哪一種情形。

來源的序號是「本站的第幾班」而不是車次,所以一班車靠時間串起來:本站發車 之後,下一站最早的那一班就是同一列車(捷運的班距三到八分,明顯大於站距的行車 時間)。串不起來的班次寧可留白。來源只公布發車時刻,終點站因此沒有時刻, arrival_times 一律留空。

通過月台的速限

機場捷運的直達車會通過許多車站。通過不停靠的車站時,月台範圍的速限為 每小時 70 公里;接近時會提前提醒並說出站名(「前方三重站月台,本站通過, 距離…,通過速限七十公里」),而不是只報一個看不出理由的速限數字。

這是班次的性質而不是軌道的性質:同一段軌道,直達車通過時限速 70,普通車 停靠時不受這條限制,因此寫在線別設定而不是路網的區間速限裡。台北捷運各線站站 停車,沒有通過的情形,所以沒有這項設定才是正確的。

自動駕駛與 ATO

只有捷運有(Alt+Shift+U 切換)。臺鐵按下會回覆「本系統沒有自動駕駛功能」, 不會靜靜地沒反應。

路線 電腦負責 駕駛負責
一般捷運線 加速、巡航、進站停車、停妥後保持制軔 開關車門,關門後按發車鍵
文湖線、環狀線、三鶯線 全部,含車門與發車 不需要任何動作

發車鍵是 F4;自動駕駛中電門鍵(Z)也是發車鍵,因為 ATO 已經在控制電門了。 車門開著時按發車不會動作。緊急制軔永遠優先,ATO 不會覆蓋它。

無人駕駛線目前固定開左側車門:維基百科的車站表沒有月台配置與開門方向, 依規格 §22、§27 不得自行推測,因此明說這是測試值而不是假裝知道每一站的方向。

重新匯入捷運資料

新站通車或營運模式調整時,改 data/mrt/line_spec.json 再重跑一次即可:

python -m railway_sim.dataset --system mrt --source "…/mrt" --dry-run

--source 指向存放各線維基百科條目網頁存檔的資料夾。與臺鐵的匯入一樣, 寫入前會先在暫存目錄用遊戲本身的載入器驗證過,驗證不通過就完全不動正式資料。

時刻表是另一個來源(條目裡沒有時刻),改點時單獨重跑:

python -m railway_sim.dataset --system mrt --timetables "…/mrt 時刻表" --dry-run

指向存放北市府資料平台時刻表 CSV 的資料夾(Big5 編碼的「北市平台版」)。 只會改動班次的發車時刻與時刻摘要,車站、路網、停靠表都不動。兩個來源可以 一起給,此時會先由條目重建資料集再補時刻;只重跑條目匯入時,已匯入的時刻 會被保留(停靠站沒變的話)。


介面選擇

專案提供兩種介面,運轉邏輯與可做的事完全相同,介面只負責送入按鍵與輸出文字。 行前提要、列車狀態與所有播報文字都來自 DriverSession,沒有任何一邊自己組字串 (§25.5:必要資訊不得只存在於單一介面)。tests/test_wx_app.py 會比對兩邊註冊的 動作集合完全一致。

主控台(預設,--ui console)

規格 §26 指定的終端遊戲。這是目前唯一可保證螢幕閱讀器行為的介面:畫面只在有事件時輸出新的一行純文字,NVDA 會自動朗讀主控台的新增內容,且不會因持續重繪造成語音塞車。

wxPython(--ui wx)

規格 §5.1 建議的 GUI。版面為兩個清單(運轉播報、狀態查詢結果),上下鍵一次讀完整一則;按鍵以 EVT_CHAR_HOOK 在視窗層級攔截,焦點在哪個欄位都能操作。

python -m railway_sim --ui wx

狀態改為「查詢才出現」:先前的版本常駐顯示整份列車狀態,玩家得自己在十幾行 文字裡找需要的那一行,內容一變螢幕閱讀器又會整段重讀。現在改成與 OpenBVE 無障礙模式相同的做法——按快捷鍵(S 速度、G 前方號誌…)或用「狀態查詢」 選單挑一項,才會播報並顯示那一項,狀態欄顯示的就是剛剛播出去的那一句。

顯示的是查詢當下的快照,不會自己更新(播出去的語音也是快照,兩者若不一致反而 更難判斷);要最新的數值就再按一次。選單存在的理由是不必先記住快捷鍵:查得到 有哪些項目可以問,也看得到每一項對應哪個鍵。要一次看完整份狀態時,用「系統」 選單的「完整列車狀態」或暫停選單的第 3 項。

沒有指定 --service 或 --scenario 時(安裝版直接執行就是這種情形),會先開啟 車次選擇視窗:上方是搜尋欄,可用車次、車種、車輛型式、起訖站或沿途車站過濾; 下方是清單,上下鍵選擇、Enter 開始。遊戲中按 Esc → 「選擇其他車次」可以隨時換車, 不必重開程式。

暫停選單的項目與主控台一致(繼續運轉/快捷鍵說明/列車狀態/狀態查詢/ 點字即時顯示/離開遊戲),另多一項「選擇其他車次」。未綁定的按鍵一樣會回覆 說明(§7.2)。

儀表板不是編輯區:先前兩個欄位是唯讀的多行文字框,而文字框對螢幕閱讀器 而言是「編輯區」——焦點一進去就被報成「編輯 唯讀 多行」,方向鍵讀的是游標所在 的行或字元,可是駕駛台上根本沒有東西可以編輯。現在改用清單:焦點落在上面報的 是「清單」與目前這一項,上下鍵一次讀完整一則播報,字母鍵也不會被當成輸入吞掉。 說明與完整狀態那類「一整段文字」仍然用唯讀文字框顯示,那時要的正是逐字逐行的 檢閱。

新播報進來時不會移動選取項目:播報已經直接送到螢幕閱讀器了(見下節),再 移動選取只會讓同一句被唸兩次,也會把正在往回查看的人拉走。清單只把捲軸移到最新 一則,讓看得見的人也跟得上。

Tab 與 F10 交還給視窗處理。先前它們會被當成「未設定功能的按鍵」而唸一句 說明,等於每次換焦點都被打斷一次。

選擇車次時,清單下方那段補充說明(路線、停靠幾站、首末班)會直接唸出來; 搜尋時也會說出還剩幾個符合。螢幕閱讀器在清單裡上下移動時只會唸出項目本身, 不這樣做的話那段說明看得到卻聽不到,除非每選一個就 Tab 過去確認一次。

模擬時間依 time.perf_counter() 的實際經過時間推進,不是照計時器間隔猜; 開啟說明或暫停選單時暫停推進,恢復時重設計時基準,因此不會在恢復的瞬間把列車 往前補推一大段。

螢幕閱讀器輸出(NVDA)

Windows x64 發行版會在套件的 railway_sim/lib 內附上 NVDA 官方 nvdaControllerClient.dll(NVDA 2026.1.1、LGPL-2.1),播報便會直接送給 NVDA 的語音與點字,不必等螢幕閱讀器自己發現畫面變了。DLL 能載入時,遊戲會 保留後端並以 testIfRunning 動態判斷 NVDA 是否已連線:因此可以先開遊戲再開 NVDA,也能在 NVDA 重啟後自動恢復。沒有 DLL 或沒有 NVDA 時所有資訊仍以純文字 完整呈現,遊戲功能不受影響(§20.1)。

能力是逐一探測出來的,缺哪一項就少用哪一項,不比對版本號:

函式 起始版本 用途
speakText、cancelSpeech、brailleMessage 全部 語音與點字
speakSsml 2024.1 帶優先級的語音
getProcessId 2024.1 確認 NVDA 換過人
isSpeaking 2026.3 查詢是否正在說話

有 speakSsml(NVDA 2024.1 起,含 2026.2)時優先走它,把優先級交給 NVDA 排: 超速與緊急制軔用 SPEECH_PRIORITY_NOW 插播,接近車站與停車位置倒數用 NEXT,其餘用 NORMAL。這比「cancelSpeech 之後再 speakText」好的地方 很實際——插播結束後 NVDA 會把被打斷的內容接回去,而不是整段丟掉,超速警告 因此不會讓正在唸的「下一站」整句消失。舊版 NVDA 會讓這些函式回傳 RPC_S_UNKNOWN_IF(1717),本模組據此自動退回 speakText。

DLL 一律以絕對路徑載入,並限制相依 DLL 只在 System32 與 DLL 自身所在目錄 尋找(防 DLL 綁架);環境變數 RAILWAY_SIM_NVDA_DLL 可指定另一個絕對路徑, 相對路徑會被忽略。

點字即時顯示(Alt+Shift+T)

點字顯示器上持續顯示距離下一站還有多遠:

永寧 1.5km          ← 一般行駛
永寧 停 47m          ← 進到最後兩百公尺,改報距離停車位置
永寧 過3.0m          ← 停過頭了(不可倒車,兩者要能一摸就分得出來)

每 0.7 秒重送一次(NVDA 把點字訊息當成暫時訊息,過幾秒就會換回焦點的內容)。 有播報進來時讓它停留 2.5 秒再切回距離,否則摸讀的人來不及讀完就被蓋掉。開啟 說明或暫停選單時停止重送——那些視窗是拿來讀的。

終端機收不到 Alt 組合鍵,因此主控台改由暫停選單的第 5 項提供同一個功能, 走的是同一個動作代碼。沒有連接 NVDA 時按下去會說明原因,不會靜靜地沒反應。


快捷鍵

功能相同者,鍵位一律與 OpenBVE 相同,以便在兩者之間切換時不必重新適應。 對照來源為 OpenBVE 的 assets/Controls/Default.controls,修飾鍵位元 Shift=1、Ctrl=2、Alt=4(OpenBveApi/Interface/Input/KeyboardModifier.cs)。

鍵位由 data/keymap.json 載入,可直接修改,程式不硬編碼按鍵。安裝版要自訂鍵位時,請複製整個 data 資料夾並透過 --data-dir 或 RAILWAY_SIM_DATA_DIR 指定它。 遊戲中按 F1 隨時查詢。

駕駛操作

按鍵 功能 OpenBVE 指令
Z 增加電門段位(0–5) POWER_INCREASE
A、, 回到惰行或降低控制段位 POWER_DECREASE、BRAKE_DECREASE
. 增加常用制軔段位(0–7) BRAKE_INCREASE
Q 單手把減速一段 SINGLE_BRAKE
/ 緊急制軔 BRAKE_EMERGENCY
Enter 鳴笛 HORN_PRIMARY
F 方向把手往前進方向移動一段 REVERSER_FORWARD
V 方向把手往後退方向移動一段(倒退) REVERSER_BACKWARD
F5 開關左側車門 DOORS_LEFT
F6 開關右側車門 DOORS_RIGHT
R 鬆軔 無對應
E 解除緊急制軔(須列車停妥) 無對應
Alt+Shift+U 啟動或解除自動駕駛(僅捷運) 無對應
F4 自動駕駛發車(關門後啟動列車) 無對應

本專案的減段是單一動作(降低目前作用中的控制),對應 OpenBVE 分開的兩個 緩解指令,因此 A 與 , 都叫到它。

自動駕駛與發車鍵是 OpenBVE 沒有的功能(臺鐵也沒有 ATO),因此鍵位由本專案自訂: 切換鍵依使用者指定為 Alt+Shift+U。自動駕駛中 Z 也是發車鍵——ATO 已經在控制 電門,再手動加一段沒有意義,而「關門之後啟動列車」正是駕駛在自動駕駛下唯一要做 的操作。

Q 與 A 方向相反:A 往中立走(減電門,電門為零之後減制軔),Q 往制軔走 (減電門,電門為零之後加制軔),因此從全電門一路按 Q 就是 P5…P1 → 惰行 → B1…B7。行為取自 OpenBVE 的 source/TrainManager/Handles/CabHandles.cs 對 Command.SingleBrake 的處理。

F/V 是方向把手,一次移動一段,順序為後退 → 切 → 前進,與 OpenBVE source/TrainManager/Handles/CabHandles.cs 的處理相同。把手在「切」時電門不會 產生牽引力;停過頭要退回停車位置,就是停妥後按兩次 V 退到後退位再加電門。 工作階段一開始把手就在前進位(本專案從「列車已備妥、等著發車」開始,不含 出庫與換端)。列車必須停妥才能改變把手——真實運轉本來就是如此,而且本專案 不模擬坡度與溜逸,行進中換方向會得出沒有意義的狀態。按 T(列車狀態)會說出 把手目前在哪一格:加了電門卻不走,多半就是它在「切」。

F5/F6 是整側車門的開關,同一個鍵開也關。連鎖規則只做可以確定的兩條: 列車未停妥不得開門、車門開啟中不得加電門(§16.2 出發流程)。不做「開門 側必須是月台側」——月台側別沒有可靠的公開來源(§27),與其猜一個假資料再據以 判違規,不如由玩家自行選邊。單門開啟與最後一門確認屬於列車長模式(§16.1), 第二階段之後才實作。

狀態查詢

OpenBVE 的無障礙指令使用 Shift+Ctrl+字母,本專案沿用;括號內為單鍵別名。 按下去才會播報並顯示該項,兩種介面都是如此;wx 版另有「狀態查詢」選單, 主控台版在暫停選單的第 4 項,項目與內容完全相同(§25.5)。

按鍵 功能 OpenBVE 指令
Ctrl+Shift+S(S) 播報目前速度與允許速度 ACCESSIBILITY_CURRENT_SPEED
Ctrl+Shift+A(G) 播報前方號誌 ACCESSIBILITY_NEXT_SIGNAL
Ctrl+Shift+T(N) 播報下一站與停靠別 ACCESSIBILITY_NEXT_STATION
Ctrl+D(D) 播報距離停車位置多遠 MISC_DIST_NEXT_STATION
P 播報目前位置 無對應
T 播報列車狀態 無對應
B 播報車門狀態 無對應

只從選單查詢(沒有專屬快捷鍵)的還有「車次資訊」與「運轉摘要」;快捷鍵不必 為了湊齊項目而占用一堆字母鍵。

主控台的限制:終端機只收得到控制字元,無法區分 Ctrl+S 與 Ctrl+Shift+S。主控台把控制字元一律視為 Ctrl+Shift+字母;由於本專案沒有 使用單獨的 Ctrl+字母,在本鍵位表內不會產生歧義。單鍵別名在任何介面都能用, wx 介面則收得到真正的修飾鍵狀態。

速度查詢的單鍵別名原本是 V,V 讓給 OpenBVE 的 REVERSER_BACKWARD 之後改用 S——正好就是 OpenBVE ACCESSIBILITY_CURRENT_SPEED 的基礎鍵。driver_legacy 配置的 S(減段)同步改為 X。

系統

按鍵 功能 OpenBVE 指令
F1 快捷鍵說明 無對應
F2 重複播報最近一則訊息 無對應
Alt+Shift+T 開關點字即時顯示(距離下一站) 無對應
Esc 暫停選單(含離開遊戲) MENU_ACTIVATE

與先前配置的關係

先前依 chat.md 訂下的是「D 電門、A 制軔、Space 緊急制軔」。改採 OpenBVE 鍵位後 A 由制軔改為減段,兩者確實衝突;依「相同功能需與 OpenBVE 相同」的 指示以 OpenBVE 為準。舊配置完整保留在 profile driver_legacy:

python -m railway_sim --keymap-profile driver_legacy

keymap.json 中每個鍵位都以 source 欄位記錄依據(沿用 OpenBVE 的哪一條, 或是規格的哪一節)。衝突檢查由 Keymap.conflicts() 執行,tests/test_keymap.py 強制其為空並逐條比對 OpenBVE 的鍵位,啟動時也會再檢查一次。


對準停車位置

看不見月台標記的駕駛在最後一百公尺裡沒有任何連續的資訊可用,只能反覆按鍵去問。 因此對位這件事有四條路,各自獨立可用:

接近時自動倒數。 距離停車位置 200 公尺起開始報,愈近愈密 (200、150、100、70、50、35、25、20、15、10、7、5、3、2、1 公尺),車頭到達 時說「停車位置。」——對位最關鍵的一句,因為不必自己從遞減的數字推算「就是現在」。

接近永寧,距離二百五十公尺,準備停車。
距離永寧站停車位置二百公尺。   ← 第一句說清楚接下來報的是什麼
一百五十公尺。                 ← 之後只有數字:最後幾公尺每句只隔一兩秒
…
三公尺。
二公尺。
一公尺。
停車位置。

播報的是當下的實際距離而不是門檻值;一個步長內跨過好幾個門檻(速度還很快) 只會報一次,不會連珠炮。

隨時可問。 Ctrl+D(或單鍵 D)報距離停車位置多遠,距離愈近解析度愈細 (一百公尺以上取整十、十到一百逐公尺、十公尺以內半公尺)。停過頭時說法不同 (「已超出…」而不是「距離…」),因為兩者該做的事相反。

點字上一直看得到。 Alt+Shift+T,見上一節。

停妥後還能修正。 未達停車位置時列車可以再往前推一點,真實運轉上也是這樣 處理。每動一次就重新播報現在差多少,並給出評價(準確/良好/可接受/偏差過大), 不必自己換算:

永寧站停妥,未達停車位置四公尺,可接受。可再前進四公尺。
修正後:未達停車位置二點五公尺,可接受。可再前進二點五公尺。
修正後:停車位置準確。

「可再前進多少」只在未達時說:超出了不可倒車(§9.2),提了也沒用。修正不會 改變已經判定的停靠結果,只更新記錄下來的誤差;離開停車範圍就結束修正。


車上廣播

規格 §20.2 的「到站廣播」。目前收錄:

  • 臺鐵:縱貫線北段、山線、海線、縱貫線南段、屏東線、南迴線、臺東線、沙崙線, 加上不屬於任何車站的十七則(七型車的開關門聲、開門側、請勿上車、臨停), 共 382 個音檔。北迴線、宜蘭線、平溪深澳線、集集線、內灣六家線尚無來源資料
  • 捷運:文湖線、淡水信義線(含新北投支線)、松山新店線(含小碧潭支線)、 板南線、三鶯線、桃園機場捷運,175 個音檔(環狀線與中和新蘆線尚無音檔)

兩邊的音檔放在同一個資料夾,依 line_id 分子目錄,因此線別代碼不會相撞; 不屬於任何車站的廣播放在 common/。

播放規則(臺鐵)

種類 檔名 何時播
下一站 <站>.next 列車自車站離站之後播,內容是下一個停靠站
到站 <站>.arrive 到達停靠站之前(1500 公尺)播
終點 <站>.terminus 到達終點站之前播;該站有終點廣播就不播到站,沒有則回退播到站
開門側 DOOR.side.left/.right 接在到站廣播之後,提醒往哪一側下車
車門聲 DOOR.open/DOOR.close 開關門時播,可加車輛型式版本(見下)
請勿上車 NOTICE.do_not_board 全車對號列車開門中持續播放,關門立即停
臨停 NOTICE.unscheduled_stop 停在月台以外的地方時播

一律以「下一個停靠站」為準,不照路線上的車站順序推進——自強號、區間快會 通過許多車站,照順序播就會播出根本不停的站。這條規則對區間車(站站停)同樣 成立,因此不需要為車種分開處理。

「下一站」是離站之後才播,不是「動了就播」:停短了往前推一點修正停車位置 時列車也在動,那時播下一站,旅客會在車門還沒關的月台上聽到它。界線是車頭有沒 有通過本站的停車位置。

每一型車自己的開關門聲

開關門聲每一型車不一樣,因此以車輛型式當版本查詢:EMU900 找 DOOR.open.emu900,找不到就退回通用的 DOOR.open,通用檔也沒有就不出聲——不會 因為多了一型反而整個播不出來,也不會拿別型的聲音頂替。新增一型車只要把音檔放進 common/,程式不必改。

目前錄到 EMU500、EMU700、EMU800、EMU900、TEMU2000(普悠瑪)與 PP(推拉式自強號) 的開門與關門聲,另有 EMU3000 的關門聲。EMU600、TEMU1000、DR1000、DR3100 與莒光號 尚無音檔,這幾型開關門時只有文字播報。區間車是共通運用(見上),因此同一個車次 今天與明天聽到的開關門聲可能不一樣。

請勿上車

全車對號的 TEMU1000、TEMU2000、EMU3000 在車門開啟中持續播放「請勿上車」, 提醒月台上沒有買這班列車車票的旅客不要上車;關門動作一開始就立即停止,不會等 這一輪播完。哪些車型要播寫在 trains.json 的 boarding_notice,不是寫死車型 代碼。

開門側怎麼決定

月台側別沒有可靠的公開來源(§22、§27),因此不假裝知道每一站的月台在哪一側, 而是依規則推:標準情況一律左側;這班車在某一站比同車種的其他班次待得 明顯久,表示它在該站待避(讓後面的車先過),開門側改為右側。

比較的是「前一停靠站 → 本站」的時間差——時刻表印的是開車時刻,待避多出來的 幾分鐘就落在這個差值裡。門檻同時看絕對值(4 分鐘)與比例(25% 以上),集中在 src/railway_sim/timetable/door_side.py 的兩個常數,要調整只改那裡。這是推導的結果,不是臺鐵公布的月台側別。

八堵、七堵的「下一站」廣播有往基隆/往花蓮兩個版本,差別在於提醒旅客要不要在 本站換車,因此取決於本班列車開往哪裡。挑選規則寫在 data/audio/announcements/variants.json, 多一個分歧站只要加一筆,程式不需要改。挑不出版本時寧可不播音檔(播錯方向比 不播更糟),文字播報照常送出。

DR1000 型柴油客車沒有車上廣播設備,因此這型車不播廣播,也不送出廣播文字—— 沒有播出來的東西不應該假裝有。這是 trains.json 的 has_broadcast 欄位決定的, 不是寫死車型代碼。

播放規則(捷運)

捷運多了三種臺鐵沒有的廣播,而且每一條線不一樣。規則寫在 data/mrt/broadcast_rules.json,程式不寫死站號。

種類 何時播
往○○ 台北捷運五條高運量線在發車之前、列車還沒啟動時播;文湖線在發車之後播。音檔以終點站代碼命名(R28.destination 就是「往淡水」),因此同一條線的不同營運模式自動播到正確的那一則
到站 到達停靠站之前(400 公尺)播
下一站 只有三鶯線(每一站都有)與機場捷運(A1、A2、A3、A18,以及終點為機場第二航廈的直達車在 A13)有;台北捷運各線沒有,因此連文字都不送出
宣導 在指定的兩站之間、指定的方向上播一次

同一站常常有好幾個版本,挑哪一個取決於這班車的終點、起站與車種:

  • 大安是終點時播終點版,途經時播正常版;台電大樓、亞東醫院、昆陽、 環北同理
  • 奇岩在終點為北投的列車上播特別版(提醒下一站就是終點),其餘列車播正常版
  • 北投從新北投開來時播支線版;七張從小碧潭開來時播支線版
  • 機場第二航廈:終點為此站的直達車有專用的下一站與終點廣播,普通車只播一般到站
  • 宣導:淡水信義線在竹圍到紅樹林之間播;松山新店線只在往台電大樓的列車過了 中正紀念堂之後播(往新店的不播)

捷運的到站廣播用 400 公尺而不是臺鐵的 1500 公尺:最短的站距只有 510 公尺 (文湖線木柵至萬芳社區),照抄會在前一站還沒開走時就播下一站的到站廣播。

更新廣播

完整流程、檔名規則與新增路線的步驟見 docs/broadcast-audio.md。

索引由掃描資料夾建立,不讀任何清單檔:把檔案放進 data/audio/announcements/<路線>/ 就會生效,檔名規則是 <車站代碼>.<種類>[.<方向版本>].<副檔名>(例:TAIPEI.arrive.ogg、 BADU.next.keelung.ogg)。因此廣播改版、新站通車都不必改程式;暫時沒有廣播 的車站只是查不到,不會讓程式出錯。

從整理前的來源資料夾匯入(會自動去掉站序編號、修正來源檔名的錯字):

python -m railway_sim.audio import --dry-run "…/台鐵廣播/1縱貫北" "…/台鐵廣播/2山線"

確認無誤後拿掉 --dry-run 實際匯入;內容沒變的檔案不會重寫,所以重跑是安全的。 對應規則放在 data/audio/source_map.json:新增一條線 只要在 line_ids 加一筆,修正一個錯字只要在 station_aliases 加一筆。已修正的 來源錯字有:福州→浮洲、楠樹林→南樹林、普興→埔心、三性橋→三姓橋、麗玲→栗林、 南市→南勢、經五/經舞蹈站→精武。

查詢目前索引到哪些廣播、哪一站還缺:

python -m railway_sim.audio check

捷運的廣播走同一個指令,只是換一個 --system:

python -m railway_sim.audio --system mrt import "…/mrt/廣播/淡水信義線"

捷運的來源檔名只有各線自己的播放序號加站名(19-1奇岩.ogg),序號與播放時機的 關係是每一條線各自約定的,站名也有錯字(籠埔實為龍埔、巷山實為象山、明權西路 實為民權西路),沒有規則可循。因此 data/mrt/audio/source_map.json 用逐檔對照: 每一則廣播要對到哪一站、哪一種時機,都在那裡看得見,程式仍然不寫死任何站名。

播放後端

臺鐵廣播是 Ogg Vorbis,Windows 內建的播放元件(wxPython 用的 Media Foundation) 不解這個格式(本專案實測過),因此播放後端依序嘗試:

  1. pygame(pip install railway-sim[audio])— 內建 SDL_mixer,可直接播 Ogg。
  2. ffplay(ffmpeg 隨附,在 PATH 上就能用)。
  3. 都沒有 → 只播文字。

依規格 §20.1「不可只靠音效表達必要資訊」,每一則廣播都會同時送出一行文字, 因此沒有後端、沒有音檔、或用 --no-audio 關掉聲音時,玩家得到的資訊完全一樣。 文字是廣播內容的摘要而非逐字稿:實際音檔含國語、臺語、客語與英語,逐字稿 無法由檔名得知,寫成摘要才不會虛構內容(§2.3)。


成功/追分/彰化路線模型

這是規格 §10 與 chat.md 反覆修正的部分,也是本專案最嚴格保護的規則。

模型依 chat.md「海線倒插在成功南、彰化北」建立:成追線在成功站南方、彰化以北的分歧 JCT_CHENGZHUI 朝成功方向倒插接入山線。

              臺中
               │(山線)
              成功
               │
        JCT_CHENGZHUI ─── 追分 ─── 海線
               │              │
               │              │(海線)
        JCT_CHANGHUA_N ───────┘
               │
              彰化

因為聯絡線朝北接入,由追分進入該分歧後只能往成功,不能直接轉向彰化。這個限制無法用「有連線就能通行」表達,因此在分歧節點上以 allowed_transitions 明確列出可行轉向。四條規則於是成為拓撲本身的性質:

情境 正確路徑 禁止
海線往彰化 追分 → 彰化 經成功(§10.2、§25.8)
海線往臺中 追分 → 成功方向 → 臺中 經彰化(§10.3、§25.9)
臺中/豐原往海線 成功 → 追分 → 海線 經彰化(§10.4)
彰化往海線 彰化 → 追分 經成功
山線列車 彰化 ↔ 成功 ↔ 臺中 經追分(chat.md 五)

防線有兩層,tests/test_route_rules.py 兩層都測:

  1. 拓撲層:錯誤路徑在 Route.build() 就會擲出例外。
  2. 規則層:即使繞路繞得出來,validate_route() 也會攔下。

實際時刻表匯入後,這些規則不再只由測試資料驗證,而是由 953 個真實班次 驗證:其中 28 班以車次後綴「追」標示經由成追線,全部走 臺中 → 成功 → 追分 → 海線 且沒有任何一班經過彰化。

成功站停靠規則

先前依 chat.md「成功站資料修正」推定,現已由 115 年 7 月 1 日實施的實際 時刻表確認:

車種 實際時刻表 先前推定
區間車 停靠 停靠 ✓
區間快 一律通過(無任何一班停靠) 「依班次設定,一般通過」
自強號(含普悠瑪、太魯閣、自強3000) 一律通過(§25.7) 一律通過 ✓
莒光號 不行經成功站 「預設通過,待正式時刻表確認」

stop_rules 現在由時刻表中實際辦客的班次統計而得,不再是人工推測; 但它只表示「該車種在本站至少有一班列車停靠」。某一班次停或不停,一律以 該班次的停靠表判斷,絕不由「列車行駛於這條路線」推論(§9.1)。 把自強號設成成功站停靠站的班次資料會被 validate_service() 擋下並使啟動失敗。

先前一項待確認的建模判斷,已由實際資料確認

chat.md 中的示意圖把山線寫成「彰化 ─ 大慶 ─ 臺中」而省略成功,與規格 §10.1 「成功是山線車站」不一致;當時採用規格的寫法。

實際時刻表證實規格是對的:成功是山線車站,山線列車會經過成功(停或不停 看班次),共有 66 班對號列車與 14 班區間快行經但不停靠。山線南下實際順序為 臺中 → 五權 → 大慶 → 烏日 → 新烏日 → 成功 → 彰化。


時刻表資料

遊戲資料由臺鐵公布的 .ods 時刻表匯入產生,目前為 115 年 7 月 1 日(2026-07-01)實施版本:全臺 240 站、953 個車次、307 條路線。

改點時不需要手動編輯 JSON,把新檔案放進一個資料夾再跑一個指令即可:

python -m railway_sim.dataset --source <時刻表資料夾>

完整流程、資料可信度與少數需人工維護的部分,見 docs/timetable-import.md。

車種與車輛型式

車種(service_class)決定停靠規則,車輛型式(train_type)決定運轉性能,兩者分開。 「自強號」是車種不是車輛型式:時刻表的車種欄把 EMU3000 印成「自強3000」、普悠瑪與 太魯閣各自成一欄,其餘印「自強」,實際上是四種完全不同的車輛。

時刻表車種 車輛型式 營運速度 常用/緊急減速度 班次數
自強3000 EMU3000 型電聯車(12 輛 6M6T) 130 3.6/4.32 km/h/s 80
自強 推拉式自強號(2M12T) 130 3.6/5.5 km/h/s 39
自強(人工確認 4 班) DR3100 型柴聯車(3 輛) 110 3.0/3.0 km/h/s 4
普悠瑪 TEMU2000 型電聯車(8 輛 4M4T) 130 3.6/4.32 km/h/s 32
太魯閣 TEMU1000 型電聯車(8 輛 4M4T) 130 3.6/4.32 km/h/s 6
區間車/區間快(電氣化區間) EMU500/600/700/800/900 五型共通運用(見下) 110~130 各型不同 682
區間車/區間快(非電氣化支線) DR1000 型柴油客車(3 輛) 110 2.448/2.448 km/h/s 103
莒光 莒光號(電力機車牽引客車) 110 無來源,全部為測試值 7

另外收錄 DR3100 型柴聯車(柴聯自強號,營運速度 110、減速度 3.0 km/h/s)。它 2023 年 4 月 26 日改點後退出西部幹線與宜蘭線,僅保留於東部幹線;115 年 7 月 1 日的時刻表無法 從車種欄分辨哪些「自強」由它擔當(與推拉式自強號共用同一個車種名稱),因此改為人工 確認車次 209、221、238、246(樹林-花蓮、蘇澳新-樹林、南澳-樹林)由它擔當,逐車次 覆寫(見下方「非電氣化支線一律是 DR1000」小節);DR1000 的牽引性能則由這份來源推算 而來。

非電氣化支線一律是 DR1000

只要班次的停靠或通過表碰到下列任何一站,車輛型式就是 DR1000——即使它有一大段行程 跑在電氣化幹線上(例如彰化直通車埕的集集線區間快):

支線 車站
集集線 源泉、濁水、龍泉、集集、水里、車埕
內灣線(竹中至內灣) 上員、榮華、竹東、橫山、九讚頭、合興、富貴、內灣
平溪線 大華、十分、望古、嶺腳、平溪、菁桐
深澳線 海科館、八斗子

清單只列支線本身的車站。二水、三貂嶺、瑞芳都在幹線上,停靠它們的幹線列車不受 影響。六家線(竹中至六家)已電氣化,新竹至六家的 82 班區間車是電聯車,屬於下面 的共通運用池。

判斷用的車站集合來自完整的行經路徑(尋路算出的每一個節點),不是只看時刻表印 出時刻的停靠站——對號列車時刻表只印主要車站,實際尋路可能補入時刻表沒印出來的 中間站,這些站一樣是列車真的會經過的地方。

車次 209、221、238、246(自強號,樹林-花蓮/蘇澳新-樹林/南澳-樹林)另外人工 確認由 DR3100 型柴聯車擔當,優先於車種與支線判斷——時刻表的車種欄無法分辨 「自強」底下同時有推拉式自強號與 DR3100 兩種車輛。

區間車與區間快:五型共通運用,每天重抽

臺鐵的通勤電聯車是共通運用:EMU500、EMU600、EMU700、EMU800、EMU900 五型在同一段 路線上混跑,今天跑某一個車次的是哪一型,隔天不一定一樣。時刻表也沒有印車輛型式, 因此「2712 次一定是 EMU800」這種寫法本身就是錯的。

匯入時填進 timetables.json 的 EMU900 只當作「這一班屬於共通運用池」的標記,真正 開哪一型由 trains.json 的 rolling_stock_pools 決定,規則與比重全在資料檔裡,程式 不寫死任何車型代碼或路線代碼。

路段 可能的車輛型式
花東線(臺東線) 只有 EMU500
沙崙線 只有 EMU500、EMU600
六家線 只有 EMU500、EMU600
其他路線 五型都有,比重依路段而不同

比重的算法是該型現役車輛數 × 該路段的配屬倍率:

車輛型式 現役車輛數 來源
EMU500 336(84 組 ×4 輛) 維基百科
EMU600 56(14 組 ×4 輛) 維基百科
EMU700 160(20 列 ×8 輛) 維基百科
EMU800 344(86 組 ×4 輛) 維基百科
EMU900 520(52 列 ×10 輛) 依採購案推定,測試值

倍率沒有公開來源(測試值),照實際運用的印象設定,抽出來的機率是:

規則 EMU900 EMU800 EMU700 EMU500 EMU600
區間快(任何路線) 65% 17% 8% 8% 1%
區間車,經過縱貫線北段 54% 18% 8% 17% 3%
區間車,其他路段 13% 39% 25% 19% 4%

抽籤以「日期+車次」做雜湊,不是亂數:同一天之內、同一個車次,車次選單、行前提要 與駕駛中查詢到的一定是同一型車(§25.5),換一天才會重抽。用 hashlib 而不是內建的 hash,因為後者每次啟動都加鹽,同一天重開遊戲就會換一台車。

「哪一天」在本次執行第一次抽籤時就定下來,之後不再看時鐘:否則在午夜前打開車次 選單、午夜後才按下開始,開到的會是另一型,長度、性能與開關門聲全都和選單上寫的 對不上。跨過午夜要換車,重新啟動遊戲即可。

各型的運轉性能差得很明顯,抽到哪一型是真的會影響開法的:

車輛型式 營運速度 起動加速度 常用/緊急減速度 編組長度
EMU500 110 2.88 km/h/s 2.84/3.12 km/h/s 81.3 公尺(4 輛)
EMU600 110 2.88 km/h/s 2.84/3.12 km/h/s(單位修正,見下) 81.3 公尺(4 輛)
EMU700 110 2.52 km/h/s(0~50) 2.88/3.6 km/h/s 163.3 公尺(8 輛)
EMU800 130 2.52 km/h/s(0~50) 4.32/4.68 km/h/s 164.3 公尺(8 輛)
EMU900 130 2.52 km/h/s(0~50) 無來源,暫用同期電聯車數值 205.6 公尺(10 輛)

兩件必須說明的資料判斷:

  • EMU600 的減速度單位有誤。來源印「常用 0.79 km/h/s、緊急 0.868 km/h/s」,照字面 採用的話從 110 公里煞到停要 139 秒、超過兩公里,任何車站都停不進去。同一組數字 換成 m/s² 恰好等於同廠同級的 EMU500(2.84 與 3.12 km/h/s),因此本專案以 m/s² 解讀,標記 derived_from_source(見 trains.json 的 meta.known_source_conflict_emu600)。
  • 不模擬重聯。EMU500/600 常態以兩組重聯成 8 輛行駛幹線,單組 4 輛行駛支線與 東部,重聯與否逐日逐班不同且無公開來源,因此編組長度一律以一組計。

路網缺一條邊會誤判成柴油客車:瑞芳-四腳亭

車輛型式判斷改用完整路徑後,揭露了一個先於本次車輛資料修正就存在的路網缺陷: routes.json 完全沒有瑞芳與四腳亭這兩個宜蘭線本線相鄰站的直接連線。深澳線在瑞芳 分歧(見下方説明),是圖裡唯一連得到四腳亭的路徑,於是任何需要經過此處的路線都被 迫繞經深澳線車站(瑞芳→海科館→八斗子→四腳亭)——連只有兩站、時刻表明確排點 6 分鐘直達的區間車都不例外,證明問題出在路網本身缺一條邊,不是任何特定車次的 判斷邏輯。這連帶讓 157 個東部幹線班次(太魯閣、普悠瑪、自強3000 等)的路徑也 「經過」深澳線車站;若只是照車輛型式判斷的新規則把它們一律改成 DR1000,會產生 另一個同樣錯誤的結果——這些真的是電聯車,不會跑深澳線。正確做法是修路網本身: 在 topology_overrides.json 的 add_station_links 補上瑞芳-四腳亭的直接連線 (remove_station_links/add_station_links 用法見上一節六家的例子),而不是 將錯就錯地改車輛型式。

資料來源政策

嚴格遵守規格 §22 與 §27。每個資料檔的 meta.data_policy 都註明來源與狀態, meta.provenance 則記錄實施日期與每個來源檔案的 SHA-256:

資料 狀態
站名、站序、車次、車種、停靠站、通過站、時刻 official — 取自臺鐵公布的時刻表
車站的車種停靠規則(stop_rules) 由時刻表中實際辦客的班次統計而得,屬推導結果
區間長度(length_m) estimated_from_timetable — 由排點時間推估,不是實際里程
車輛的最高速度、編組長度、起動加速度、減速度 取自各車型的公開技術諸元,逐欄以 field_status 標示 official/derived_from_source/test_data
行駛阻力係數(resistance_ms2) test_data — 無公開來源,改用通用的戴維斯式係數,並以「每一車型都達得到來源標示的營運速度」為校驗條件
區間速限 test_data — 明確標示的測試資料,非臺鐵實際數值
月台數、股道配置、月台長度 null + unknown — 無可靠公開來源,未推測

停車範圍(±50 公尺)、注意號誌速度(60 km/h)等亦為測試值,因為真實月台長度與號誌位置無公開來源。

trains.json 的 meta.known_source_conflict 記錄了一項來源本身的矛盾:DR3100 型的 技術諸元同時標示「起動加速度 0.5 km/h/s」與「營運速度 110 公里」,但 0.5 km/h/s (0.139 m/s²)在 110 公里時產生的牽引力小於同速度下的行駛阻力,列車根本到不了 110。 因此 DR1000 與 DR3100 的起動加速度改由同一份來源的引擎出力與編組重量推算, 標記 derived_from_source。


專案結構

src/railway_sim/
├─ app.py                    啟動入口、系統與情境選擇、啟動前驗證
├─ data_loader.py            資料載入與驗證(依系統決定資料目錄)
├─ systems.py                可駕駛的鐵路系統(臺鐵/捷運)
├─ accessibility/
│  ├─ announcer.py           播報排程:優先級、中斷、去重、重複播報
│  ├─ messages.py            所有玩家可見文字(含中文數字轉換)
│  └─ speech.py              選用的 NVDA 語音後端
├─ audio/
│  ├─ library.py             廣播音檔索引(掃描資料夾,缺檔不是錯誤)
│  ├─ player.py              選用的播放後端(pygame/ffplay)
│  ├─ broadcast.py           播哪一則:下一站/到站/終點/車門
│  ├─ mrt_broadcast.py       捷運的播放規則:往○○、宣導、終點與方向版本
│  └─ importer.py            由整理前的來源資料夾匯入並改名
├─ input/
│  ├─ keymap.py              鍵位載入、衝突檢查、說明產生
│  └─ keyboard.py            按鍵派送
├─ simulation/
│  ├─ clock.py               固定步長 0.1 秒
│  ├─ physics.py             牽引 − 制軔 − 阻力
│  ├─ braking.py             制軔操作規則與制動距離
│  ├─ doors.py               車門操作與連鎖
│  ├─ train.py               列車與車種
│  ├─ position.py            位置描述(不依賴視覺地圖)
│  ├─ atp.py                 速度監控、超速處理、冒進號誌
│  └─ ato.py                 自動駕駛(僅捷運)
├─ railway/
│  ├─ track.py               路網圖與分歧轉向限制
│  ├─ route.py               路線建構與 §10 規則驗證
│  ├─ station.py             車站與月台
│  ├─ signal.py              三顯示號誌
│  ├─ block.py               閉塞占用
│  └─ interlocking.py        進路聯鎖與衝突偵測
├─ timetable/
│  ├─ service.py             班次
│  ├─ rolling_stock.py       區間車的共通運用:每天重抽車輛型式
│  ├─ door_side.py           各站的開門側
│  └─ stop_pattern.py        停靠判斷
├─ dataset/                  資料匯入(改點或新站通車時重新產生)
│  ├─ ods.py                 只用標準函式庫讀 .ods
│  ├─ tra_parser.py          讀懂臺鐵的兩種表格排版
│  ├─ registry.py            中文站名到車站代碼的對照
│  ├─ build.py               產生臺鐵的路網、路線與班次
│  ├─ mrt_wiki.py            剖析維基百科的捷運車站表(含合併儲存格)
│  ├─ mrt.py                 產生捷運的路網、路線與營運模式
│  └─ __main__.py            匯入指令(--system tra/mrt)
├─ roles/driver.py           司機員模式(把上述全部接起來)
├─ events/                   事件匯流排、違規與特殊事件紀錄
└─ ui/
   ├─ console_app.py         主控台介面
   └─ wx_app.py              wxPython 介面

完成度

依規格 §2.3「不可虛構完成度」:以下狀態均對應實際程式碼與通過的測試。

已完成並有測試

項目 對應規格
專案骨架、資料讀取器、啟動入口 M0
電門/制軔/單手把減速/緊急制軔/鬆軔/鳴笛 M1、§8
左右側車門開關與出發連鎖 §16.2 部分
速度、位置、下一站、號誌、列車狀態、車門狀態查詢 M1、§4.1
簡化列車物理(0.1 秒固定步長) §8
停靠站/通過站/應停未停 M2、§9
區間速限與簡化三顯示號誌 M2、§11
全鍵盤操作、快捷鍵說明、重複播報 M3、§2.1
播報優先級、中斷、去重 §21
閉塞占用與紅燈防護 M4 部分、§12、§14.2
進路聯鎖與衝突偵測 §13
ATP 速度監控、超速警告與違規、冒進號誌 §14
成功/追分/彰化拓撲與路線驗證 M5 部分、§10
全臺路網與實際時刻表(240 站、953 車次) §22、§27
時刻表匯入工具(改點時可重新產生資料) §22、§27
十二種車輛型式的實車性能與依車種/線別分派 §22、§27
區間車與區間快五型共通運用:依路段加權、每天重抽 §22、§27
與 OpenBVE 一致的鍵位 §7
兩種介面功能對等,wx 版可選任一車次並在遊戲中換車 §5.1、§25.5、§26
單項狀態查詢(按鍵或選單才播報並顯示) §2.1、§25.5
車上廣播:臺鐵八條路線共 382 個音檔,含開門側、七型車的車門聲、請勿上車與臨停 §20.2 部分
廣播匯入工具(改版時可重新匯入) §20.2
臺鐵/捷運雙系統,開場選擇並可在 wx 版中途切換 §5.1、§25.5、§26
捷運十條路線(169 站、40 種營運模式、12 種車輛型式) §22、§27
捷運資料匯入工具(新站通車時可重新產生) §22、§27
捷運車上廣播 175 個音檔,含往○○、宣導與終點/方向版本 §20.2
機場捷運的普通車與直達車停靠規則 §9.1、§25.7
自動駕駛與 ATO(含三條無人駕駛線的全自動運轉) §8、§16.2
捷運四條線的實際逐班時刻(板南、中和新蘆、松山新店、淡水信義) §22、§27
捷運時刻表匯入工具(改點時可重新產生) §22、§27
機場捷運通過不停靠車站的月台速限 70 公里 §14.1
對準停車位置:自動倒數、隨時查詢、停妥後前進修正 §2.1、§9.2
播報直接送給 NVDA 的語音與點字,並帶優先級(SSML) §20.1、§21.1
點字即時顯示距離下一站(Alt+Shift+T),兩種介面都有 §20.1、§25.5
wx 儀表板改用清單,不再是螢幕閱讀器眼中的編輯區 §2.1、§5.1

測試:724 項,python -m pytest 全數通過(其中 wx 介面的 57 項在未安裝 wxPython 時自動跳過)。

尚未實作

  • AI 列車(§15)— 目前只能以「特殊事件」方式放置靜止的占用列車,沒有依時刻表自行運轉的 AI。
  • 列車長模式(§16)、車站運轉員模式(§17)、行車調度員模式(§18)、售票員模式(§19)
  • 音效系統(§20)的其餘部分 — 已完成到站廣播(§20.2);牽引聲、制軔聲、 車輪聲、鳴笛聲、月台環境音等仍未實作,也沒有依玩家位置與距離調整音量的機制 (§20.1)。廣播本身臺鐵缺北迴線、宜蘭線、平溪深澳線、集集線與內灣六家線, 捷運缺環狀線與中和新蘆線;開關門聲已有 EMU500、700、800、900、TEMU2000 與 PP 六型(EMU3000 只有關門聲),EMU600、TEMU1000、DR1000、DR3100 與莒光號還沒有, 這幾型會退回通用版本(目前沒有通用版本,因此只有文字)。
  • 依行駛日期篩選班次 — 來源時刻表的行駛日期符號(▲△⊕◆…)已保留在 operating_mark,但尚未實作篩選,所有班次一律視為每日行駛。
  • Windows 封裝 — 請見 docs/windows-installer.md
  • 真實月台/股道資料、真實里程與速限、真實行駛阻力係數、真實 ATP 曲線(§27:無可靠來源前不得補齊)
  • 莒光號車輛的技術諸元(目前是 trains.json 中唯一整組為測試值的車輛型式)

已知限制

  • 區間長度由排點時間推估、速限為測試資料,運轉時間不等於實際運行時間。
  • 對號列車時刻表中主要車站的「到達/開車」分列兩行,本專案只取到達時刻。
  • 號誌只有三顯示,沒有臺鐵實際的號誌種類與現示。
  • 進站/出發號誌未與月台股道連動(缺月台資料)。
  • ATP 不強制營業停靠:規格 §14.1 把「前方停車點距離」列為監控內容,本專案將其實作為提醒而非強制介入,因為營業停靠不是保安功能——停不下來屬於司機的行車違規(應停未停,§9.2),而非 ATP 應接管的情況。強制介入僅限區間速限、號誌與冒進號誌。
  • wx 介面的 NVDA 直接語音路徑未實測(見上)。
  • 時刻表的車種欄無法分辨同一車種底下的不同車輛(例如哪些「自強」由 DR3100 柴聯車擔當),因此車輛型式是依車種名稱與線別電氣化與否分派,不是逐車次的實際運用。
  • 區間車與區間快的車輛型式是依路段加權抽出來的,不是臺鐵的實際運用表:臺鐵 沒有公布逐日的車輛運用,抽籤的倍率(哪一段偏哪一型)是測試值。EMU900 的現役 車輛數也沒有技術諸元來源,是依採購案推定的。
  • 不模擬重聯:EMU500/600 常態以兩組重聯成 8 輛行駛幹線,本專案一律以一組 4 輛 計(見 trains.json 該型的 note)。
  • 捷運沒有對外公布逐班時刻,因此資料是營運模式而非時刻表,也沒有班距與發車時刻。
  • 捷運各線只登記一種代表車輛型式給每一種營運模式;實際運用是同線多型混跑, 條目沒有逐班的運用資料。
  • 捷運的月台配置與開門方向不在來源資料裡,因此無人駕駛一律開左側(測試值,§27)。
  • 三鶯線電聯車的性能全部是測試值:條目只寫出製造商與 2 節車廂編組,沒有任何 速度、長度或加減速度數據(§27)。
  • 淡水信義線來源資料中的 宣導1.ogg 目前沒有指定播放時機,已匯入保存但規則檔 尚未使用它。

About

臺灣鐵路模擬遊戲

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages