臺灣鐵路運轉主題的無障礙優先人員模擬遊戲。純鍵盤操作,所有狀態變化都有文字回饋。
可駕駛臺鐵與捷運兩個系統:臺鐵為全臺路網與實際時刻表,捷運為臺北捷運七條線、 新北捷運三鶯線與桃園機場捷運,另有 ATO 與自動駕駛。開場先選要開哪一個。
目前完成範圍:第一階段司機員模式(里程碑 M0–M3,另含 M4/M5 的部分基礎)。 其餘角色與系統尚未實作,詳見〈完成度〉一節。
依據文件:
- DEVELOPMENT_SPEC.md — 開發規格 0.1
- docs/chat_decisions.md — 對話中已確認的路線與鍵位結論
兩份文件衝突時,以 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,普通車 停靠時不受這條限制,因此寫在線別設定而不是路網的區間速限裡。台北捷運各線站站 停車,沒有通過的情形,所以沒有這項設定才是正確的。
只有捷運有(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 會比對兩邊註冊的
動作集合完全一致。
規格 §26 指定的終端遊戲。這是目前唯一可保證螢幕閱讀器行為的介面:畫面只在有事件時輸出新的一行純文字,NVDA 會自動朗讀主控台的新增內容,且不會因持續重繪造成語音塞車。
規格 §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() 的實際經過時間推進,不是照計時器間隔猜;
開啟說明或暫停選單時暫停推進,恢復時重設計時基準,因此不會在恢復的瞬間把列車
往前補推一大段。
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 可指定另一個絕對路徑,
相對路徑會被忽略。
點字顯示器上持續顯示距離下一站還有多遠:
永寧 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_legacykeymap.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) 不解這個格式(本專案實測過),因此播放後端依序嘗試:
pygame(pip install railway-sim[audio])— 內建 SDL_mixer,可直接播 Ogg。ffplay(ffmpeg隨附,在PATH上就能用)。- 都沒有 → 只播文字。
依規格 §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 兩層都測:
- 拓撲層:錯誤路徑在
Route.build()就會擲出例外。 - 規則層:即使繞路繞得出來,
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——即使它有一大段行程 跑在電氣化幹線上(例如彰化直通車埕的集集線區間快):
| 支線 | 車站 |
|---|---|
| 集集線 | 源泉、濁水、龍泉、集集、水里、車埕 |
| 內灣線(竹中至內灣) | 上員、榮華、竹東、橫山、九讚頭、合興、富貴、內灣 |
| 平溪線 | 大華、十分、望古、嶺腳、平溪、菁桐 |
| 深澳線 | 海科館、八斗子 |
清單只列支線本身的車站。二水、三貂嶺、瑞芳都在幹線上,停靠它們的幹線列車不受 影響。六家線(竹中至六家)已電氣化,新竹至六家的 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目前沒有指定播放時機,已匯入保存但規則檔 尚未使用它。