§
Simutrans 簡易タイムテーブル機能パッチ

Jun 26, 2012
wackdone


Simutransで、列車の等間隔運行や調整された退避などを実現するためのパッチです。
ここではこの機能の内容と簡単な使用方法について説明します。
(ソースへのパッチの適用などについては README_TIP_ja.txt をご参照ください。)


(鉄道以外の乗り物にも適用できているはずですが、
テストは鉄道でしか行っていません。また以下、乗り物全般を指す意味で
「列車」と呼びます。)


--------------------
* 機能の概要
--------------------
** 駅間所要時間指定
列車のスケジュール設定上で
  前の駅を*出発して*から
  次の駅を*出発する*まで
の計画所要時間を指定できます。
これが指定された列車はこの計画所要時間をできるだけ守るように運行します。

これを適切に設定することで、間隔を空けて出発させた各列車はその間隔を
維持しながら運行します。

** 発時刻指定
また追加機能として、スケジュールの先頭では列車の発車を時刻で指定できます。
同じ路線(line)が適用された複数の列車で、この発車時刻だけをずらすことで
調整された周期的ダイヤ (のようなもの) を再現することができます。


--------------------
* 計画時間運行の仕組み
--------------------
スケジュールの各駅には前駅の出発から本駅の出発までの計画スプリット時間
(ttt_plan) を設定できるようになっています。

(中継点にも入力できてしまいますが、現状では無視されます)

また各列車は、
  前駅の出発時刻 (last_departure)
  積算遅延時間 (delay)
を記憶しています。

遅延や早着が発生した場合には、積算遅延時間に繰り込み
その先の運行でこれをゼロにするよう調整します。

具体的には以下のような計算と調整を行います。

駅に到着時: 駅での停車目標時間を計算
    elapsed = 現在時刻 - last_departure
    wait_time = ttt_plan - elapsed_time - delay

  wait_time が負 (計画時間が小さすぎるか、遅延が大きすぎる) の時は、
  できるだけ早く出発できるようにする。
  wait_time が正の時は、この時間 (積載時間も含む) だけ停車する。

駅を出発時: 出発時刻と遅延時間を更新
    delay += (現在時刻 - last_departure) - ttt_plan
    last_departure = 現在時刻

実際には、シミュレーション全体の重さや、Simutrans の入れる
ランダムな処理ディレイなどがあり、
列車はほぼ常に若干の遅延状態 (delayが正) で運行してしまいますが、
計画時間の設定が無謀でなければ、この遅延が一定量におさまるよう運行します。
(内部の処理上は、wait_time から TIMETABLE_SLACK (デフォルトは 1000ticks)
 を引いて処理していますが、なかなか早発運行にはならないようです)


--------------------
* 時間の単位と最大値
--------------------
** Simutrans内の時間 (参考; ご存知の方は飛ばしてください)
Simutrans内ではシミュレーション時間の最小単位は、実時間のmsec(ミリ秒)で
管理しています。(これをコード上では tick と呼んでいます)
ゲームパラメータ bits_per_month (bpm)は、ゲーム時間での1ヶ月が何msecになるかを
log_2で表わします。

例:
 bits_per_month = 20 の時
  ゲーム上1ヶ月 = 2^20 = 1048576 msec (実時間) = 約17.5分(実時間)

またSimutrans オリジナルにおいて、積載率とともに指定できる 1/256 などの
停車時間は、この bits_per_month に対する比率を表わしています。

例:
 bits_per_month = 20 の時の 1/256 停車とはゲーム上の1/256ヶ月を意味し、
  2^20 * 1/256 = 2^12 = 4096 msec (実時間) = 約4秒 (実時間)

(なお、ここでの元になるticksの話はソースコード上の記述の問題であって、
このticksがどの程度正確なものであるかは未調査です。ご了承ください)


** タイムテーブルでの時間はTTT
この簡易タイムテーブル機能パッチでは、タイムテーブルの時間設定のために
TTT (timetable tick) という単位を導入しています。
原則としてユーザは時間を全てこの単位で設定します。

1 TTT はデフォルトでは 2^8 = 256 ticks です。
(256msec、つまり約1/4秒(実時間))

本来は計時の最小単位である tick (msec) で指定したほうが極限までの調整が
できるのですが、tickでは値が大きくなり過ぎる場合もあり、
 - 設定時の値の取り扱いが手動では面倒になる
 - 記憶量が増えてしまう (各スケジュールの各行に記録される)
という難点があるため、この単位を導入しています。

なお前述の仕組みの項で説明した last_departure, delay はどちらも
tickで管理しているため、誤差が積もる心配はありません。

また、スケジュール上に記録する時間は現在16bit符号無しで扱っています。
よって各駅間で最大 65535 ttt まで指定可能です。
(bpm=20の時で最大ゲーム上16ヶ月、bpm=22なら4ヶ月を表現可能)

なお後述するタイムテーブル周期やその中でのオフセット時間の指定は 32bit符号無しで指定できます。

課題: TTTの大きさの調整
  現状、1TTT と 1tick の比率はハードコードされています。
  (ソース内では dataobj/fahrplan.h の中の TTT_SHIFT で指定されています。)
  bits_per_month のようにゲーム設定で変更できるようにするかは検討中です。

課題: TTTの表記
  現状、ほぼ全ての状況で出現する数値はこの TTT 単位のただの整数値です。
  ゲーム時間に対応させて "h時間m分" であるとか "h時m分" であるように
  表示、入力させることはまだ考えられていません。


--------------------
* 発時刻指定
--------------------
(注意: 現状は少々ややこしいです。良い方式か良い説明が求められるところ)
一定時間運行をよりダイアグラムに近付けるための機能です。
(ソースコード上は"scatter"と呼んでいます。)

スケジュールに対してタイムテーブル周期 (timetable cycle) が指定された場合、
列車はスケジュールの先頭の項目 (中継点ではなく停車点(駅など)でなくてはだめ)
から発車する場合に限り、ゲーム内時刻を基準に発車時刻を決めます。
スケジュールには cycle が、各列車には offset がそれぞれ指定でき、列車は
  ゲーム内時刻 を cycle で割った余りが offset を過ぎた時
に発車します。
(modあふれを無視して C言語風の式で書けば
  now % cycle >= offset
 で出発します。)

これを利用すれば
  - 車庫からの出庫でピリピリしなくても列車の運行間隔を揃えられる
  - 複数の種別のあいだでcycleを揃えれば(1以上の整数倍など)
    接続や退避が狙いこめる
といった調整が可能になると考えられます。


用語の話:
  ここで言っている「タイムテーブル周期」とは、どちらかというと
  タイムテーブルの時間的長さです。繰り返し数などではありません。

  波で言えば、波長であって、周波数ではありません。
  また同様に offset は位相にあたります。

注意:
  後述しますが、運行がタイムテーブルに大きく追いつけていない場合、
  現状のロジックでは発時刻が激しく(1周期分)飛びます。
  つまり大雑把に言えば、列車1往復分運休状態になります。
  ここの調整ロジックは要検討です。
  現状はかなり余裕のあるダイヤでなければ実用にならないでしょう。


--------------------
* 使用方法
--------------------
基本的な(お勧めの)使い方は以下の手順になります。
(路線作成の有無など組み合わせ方はさまざまですが)
 (1) 路線を作成する (路線作成と編集)
 (2) 列車に路線を適用する
 (3) ひととおり走らせてみて、計画所要時間を設定する (路線編集)
 (4) 適宜、列車の遅延をリセット (列車の詳細ダイアログ)

ここから下はオプショナル:

 (5) 複数の列車(編成)に適用した場合はタイムテーブル周期と列車ごとの発時刻を設定する
     (路線編集と列車のスケジュール編集)


** 路線・スケジュール編集ウィンドウでの表示項目
路線あるいは列車個々のスケジュール編集ウィンドウ内でスケジュールを表示する部分が
次のように拡張されています。
スケジュール内の個々の行について (行が駅であれば):
  1) [150] (125) どこかの駅 (12,34,0)

駅名の前にある二つの括弧内の値はそれぞれ以下のような意味です。

  [xxx]  この駅に設定されている計画所要時間。単位はTTT。
         (前の駅の出発からこの駅の出発までの計画時間)
         Split time 入力フィールドで変更できます。(後述)
         計画所要時間が指定されていない(0)場合は表示されません。

  (yyy)  この駅の出発時に計測された実績所要時間。単位はTTT。
         (前の駅を出発してからこの駅を出発するまでにかかった時間。最新の実績値)
         実績所要時間が記録されていない場合は表示されません。


** 実績所要時間の計測
列車を車庫から出してスケジュールを一周運行させると、全ての駅間の実績所要時間が
計測されます。
(ただし運行の途中で列車を停止させてしまった場合 (スケジュール編集を開いてしまったなど) は、
 この値が正確でないかもしれません。)

この値を参考に、次項で説明する編集機能を使って、各駅間の計画所要時間を設定していきます。
(距離とスピードなどからあらかじめ別で計算できている場合は、はじめから
 計画所要時間を設定していってもかまわないでしょう)


** 路線・スケジュール編集ウィンドウでの設定項目
路線あるいは列車個々のスケジュール編集ウィンドウ内に
以下の入力項目が追加されています。

Timetable Cycle: (数値入力、単位はTTT)
  発時刻指定運行の場合のタイムテーブル周期を指定します。
  この値が 0 の場合は発時刻指定運行は行いません。
  (この値は一つのスケジュールそのものに指定される値であり、
   スケジュール行の選択には関係ありません。)

autocalc cycle: (ボタン)
  タイムテーブル周期 (Timetable Cycle) を自動計算します。
  スケジュール内の全ての駅に指定されている計画所要時間の総和を
  Timetable Cycle として設定します。
  (スケジュール行の選択には関係ありません。)

Scatter Pitch: (数値入力、単位はTTT)
  現在は使用されていません。値は0のままにしておいて下さい。
  将来実装される(かもしれない)、自動等間隔運行設定機能で使用される予定です。

Convoy Offset: (数値入力、単位はTTT)
  列車個別のスケジュール編集画面のみにあります (路線編集時にはありません)。
  発時刻指定運行を行う時に、この列車の発時刻を指定します。
  (この値は一つの列車そのものに指定される値であり、
   スケジュール行の選択には関係ありません。)

Split time: (数値入力、単位はTTT)
  選択されているスケジュール行に対して、計画所要時間を設定します。
  これが 0 の時はこの駅での出発時間調整は行われません。
  指定する時間は、前の駅から選択されている駅までの時間 (出発から出発まで) です。

clear timetable: (ボタン)
  スケジュール内の全ての行の Split time (計画所要時間) を削除 (0にする) します。
  (スケジュール行の選択には関係ありません。)


** 列車の詳細情報ウィンドウでの表示項目
列車の詳細情報ウィンドウ (列車情報ウィンドウ内で「詳細」(Detail)ボタンを押した
場合に表示されるウィンドウ) の中に、

  Delay: 12 ttt (= ...)

といった表示が追加されています。
これは現在、列車が記憶している遅延積算量です。

その右にある "Reset Delay" ボタンを押すことで、この値を0にすることができます。


--------------------
* 運行の開始
--------------------
上述の「仕組み」で説明した last_departure は、列車が車庫を出る時にクリア
(INVALIDという値になる)されます。

次の停車 (一般にはスケジュールの先頭項目) では、駅間所要時間を計測することが
できないため、停車時間を調整することができず、積載後すぐに出発します。
ここで last_departure が実際の出発時間に更新されるため、
その次の駅の停車時間調整から有効です。

ただし、発時刻指定が有効な場合はこれに関係なく出発時刻調整が行われます。


--------------------
* Simutransオリジナル機能との関係
--------------------
スケジュール編集ウィンドウでは、Simutransオリジナルの
積載率指定や積載待ち時間などの入力もそのまま残っています。
スケジュールに計画所要時間や運行周期を入力しなければ、
オリジナルどおりの動作になり、積載率指定や積載待ち時間指定が有効になります。

両方を指定している場合は:
  遅延している (停車目標時間が負): 旧来からの積載率指定などが有効
  定時内運行中 (停車目標時間が正): 計画時間による調整が有効
となります。


--------------------
* 注意点
--------------------
パッチ作者自身、この機能を充分に使い込み、試しているわけではありません。
突然、あなたのマップのあちこちの駅で列車が詰まりまくっていても、
損害賠償や振替輸送に応じることは出来ませんのでご了承ください :-)

対応しきれるかわかりませんが、問題報告、改善案、その他などなどご協力は
歓迎いたします。

コード自体を改善しての再配布はさらに歓迎です。
(ただブランチたくさんできた場合はどこかでまとめましょうね)

--------------------
* 課題
--------------------
OpenTTDのタイムテーブル機能に近いものから始めてみましたが、
夢見る「ダイアグラム設定機能」までは程遠いものがあります。

# なお、以下で「プログラム上の制限」うんぬん言っている部分は、
# 「これやろうとすると、改造箇所が多くなりすぎるんだよな。
#   クラス間の関係まで改造しちゃったりすると、パッチとしていられなくなるかも」
# という感じで躊躇してしまっているという意味です。
# 本家にまで持ち込めれば、かえって手をつけやすくなるかも。

  - なんといっても精度が悪い。間隔は大きく見れば安定しているのだが、
    発時のばらつきが大きい。また、slackを1000ticksにしても早発にならない。
    コード上は出発ロジックのところに直接判定を入れているので、
    これ以上の調整は難しいのだが、wait_lockにまで手を入れないといけないかもしれない。
    (→これを短かくすると確実に重くなる)
    使われ方によっては何らか対策を考えるべきだろう。

  - スケジュール編集ウィンドウにフィールドが増え過ぎた。
    また路線に対する設定と列車に対する設定と駅に対する設定が混じっていたり。
    タブ化するなりして整理しないと。

  - 複数の列車(編成)を同じスケジュールで等間隔に運行するにはどうするべきか？
     ← 仮に cycle と offset で実現してみた。

  - 発時刻指定運行: あまりにも遅れがでていた場合は
    ロジックの都合上、とんでもない段落ち(というのかな？)が発生する可能性がある。
    すぐには解決できないので、試用感想やロジック整理の上での議論が必要だろう。

  - 発時刻指定運行: 等間隔自動設定機能はあるといいのだろうが、
    Simutransのプログラム内部の都合がいろいろと。
    基本は pitch = cycle / nconvoi   (nconvoi は編成数)
    で、pitchによって路線内の全ての編成に何らかのルールでpitchずつずらして
    offsetをつけていけばいいの、だが、
    スケジュール編集ウィンドウから、すぐには路線にたどりつけない。
    また上の「何らかのルール」もちょっと面倒。

  - 中継点の扱い。時間を入力できないようにする、か、
    中継点までの所要時間も繰り込むようにする。
    (中継点だったところが駅になったり、駅が中継点になったりした場合に
     ある程度、所要時間の引き継ぎができた方がよいかと思う。
     また後者の場合は、今の仕様だと値がなくなってしまうので問題)

  - 現状、実績所要時間を見るには車両のスケジュール編集を、
    計画所要時間を入力するには路線編集をそれぞれ開く必要がある。
    面倒といえば面倒。対策としては、
    + 指定した列車の実績時間を路線編集側に表示する
    + あるいは、車両側で計画時間を入力したら路線に反映させる
    など考えられるがプログラム上のいろいろと制限が。

  - 現在は「駅間の所要時間」であるが、
    スケジュールの先頭を0とした「各駅の発時刻」にした方が時刻表っぽいか？
    (表示や入力の仕方の問題だけ)

  - 実績所要時間がいつもスケジュールに表示されるのは欝陶しいだろう。
    (GUI上、どこでon/offやクリアする？)

  - 厳密に駅間時間 (前駅発から駅*着*までの時間) と駅での停車時間をわけた方がいいか？
    今はこの両方をあわせて「発から発までの時間」だけで扱っている。
    わけた方が、ダイヤ設定時に考えやすくはなるだろうけど、
    プログラム的には、設定画面、データ保持ともに冗長になりそう。

  - 何かの編集の拍子に delay が突然大きな値になってしまったり、
    値の設定、コピーなどに整理できていない、把握しきれていない部分がある。

  - などなど問題が多すぎて書ききれない

  - そもそも、この文章も説明もわかりにくい orz

  - きっとイメージ貼りつけた説明書やチュートリアルがあるといいんだろうな…
    HTMLでしこしこ書くか、どこかのWikiに書くか…


--------------------
* 既知のバグ
--------------------
  - ゲームをロード直後は last_depature がリセットされてしまう。
    (判定が不十分なため車庫からの出庫と区別がつけられていない。)

