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

				Jul  2, 2012
				    wackdone
------------------------------------------------

Simutransで、列車の等間隔運行や調整された退避などを実現するための
機能追加パッチです。

列車のスケジュール上で、駅間の運行計画時間や始点での予定出発時刻を
指定することで、計画した時刻表にできるだけ沿って運行させるようにできます。

ただし「できるだけ」の運用なので、計画に対して充分な評定速度が出せていない
場合や、その他さまざまな要因で列車に遅延が出ることもあります。
遅延が出ている場合は、駅の停車時間を短くすることで回復運転を試みます。

鉄道以外の乗り物にも使用可能ですが、テストは鉄道とバスでしかしていません。
また以下、乗り物全般を指す意味で「列車」と呼びます。


--------------------
= バージョン情報
--------------------
パッチバージョン: TTT Version 0.33
対応Simutransソース: Nightly r5788
パッチファイル名: simutrans-r5788-ttt-033.patch

== 適用可能ソースについて
手元では、Release 111.3 (r5772) にも適用できる
(ただし、simversion.h だけは手であてる必要あり)、
そして動作することを確認しています。
おそらく、この間のバージョンであればどれも適用可能でしょう。


--------------------
= セーブファイルのバージョン
--------------------
本パッチを適用すると、ゲームのセーブファイルのバージョン番号が
  0.112.5
になります。
(最近のリリースである 111 よりもある程度余裕をもってこの値にしています。)

0.111.4 以前のセーブファイルをロードする事は可能ですが、
これをこのパッチのあたったSimutransでセーブすると、
セーブファイルバージョンが 0.112.5 になってしまいます。
よって、オリジナル Simutrans ではロードできないものとなります。

またこの値は近い将来 Simutrans本家で使われる可能性が高く、
その際にはロード時に想定外のクラッシュが発生します。
Simutransが 112 になる前にこのパッチが本家へマージされることを願っていてください。

なお互換性維持のため、このパッチで一時的に使用していた 0.811.5 という
番号のファイルもロードは可能です。


--------------------
= パッチの適用とビルド
--------------------
Simutransのビルドが可能な環境とスキルがあることを前提に説明します。

== パッチの適用
Nightlyのソースを入手し、これに対して同梱のパッチファイル
  simutrans-r5788-ttt-033.patch
をpatchコマンドなどを使ってあててください。

例:
{{{
  svn co -r5788 svn://tron.homeunix.org/simutrans/simutrans/trunk
  cd r5788
  patch -p0 < simutrans-r5788-ttt-033.patch
}}}

== コンフィグ
ビルドコンフィグファイル (config.defaultなど) の中に以下の行を追加してください。

{{{
  CFLAGS += -DSCHEDULE_TIMETABLE
}}}

なおソースコード上は全てこれを元にifdefで制御されており、
このマクロ定義をしなければ、動作がオリジナル通りになります。
障害時にはこの定義を切り替えて動作を比較してください。

== ビルド
オリジナルと同様に make でコンパイル/リンクできます。


--------------------
= 簡易タイムテーブル機能の概要
--------------------

== 駅間所要時間の計画
列車や路線(line)のスケジュール設定上で、列車が
  前の駅を*出発して*から
  今の駅を*出発する*まで
の計画時間(目標値)を指定できます。

これが指定された列車は、この計画時間をできるだけ守るようにしながら運行します。
これを適切に設定することで、間隔を開けて出発された同一路線上の各列車は、
その間隔を維持しながら運行します。

== 周期的な始点発時刻指定
始点駅(スケジュールの先頭の駅)では、列車の出発*時刻* (ゲーム内時間を
もとにしたタイミング) を指定できます。(オプショナル)
時刻には周期と列車ごとのオフセットを指定することができます。
これを適切に設定することで、列車のスタート時の間隔を手動で調整する必要もなくなり、
さらに、同一線路を走る複数の路線にまたがって周期を揃えて設定することで、
周期性のある簡単なパターンダイヤを実現することができます。

またこのオフセットを路線内の全ての列車に自動設定させることもできます。


--------------------
= 駅間所要時間計画運行の仕組み
--------------------
スケジュールの中のそれぞれの駅には、前駅の出発から今駅の出発までの
計画スプリット時間 (planned) が設定できます。
(中継点にも設定できてしまいますが、現状では無視されます)

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

早着や遅延が発生した場合には、積算遅延時間に繰り込みます。
列車はこの遅延時間がなるべくゼロになるように、
駅の出発時刻(go_on)を調整します。

アルゴリズムは簡単には以下のようになります。

  * 駅の停車時: 出発予定時刻の算出
    . 実績走行時間の計測: trip = now - last_departure
    . 出発予定時刻の計算: go_on = now + (planned - trip) - delay

  * 発車の判定
    . now が go_on 以上になったら発車処理
      (停車時点ですでに過ぎている場合はできるだけ早く発車しようとする)

  * 駅の発車時: 遅延時間の再計測
    . 実績駅発間時間の計測: elapsed = now - last_departure
    . 遅延時間の積算: delay += elapsed - planned
    . 発時刻の記録: last_departure = now

コード上では、これとは少し違う形の式になっていますが、
意味的には同じ計算を行っています。


== 運行の開始
last_departure は車庫を出る際に無効値に設定されます。
この直後の停車では、ここに示した出発予定時刻の計算は行われず、
Simutrans本来の「積載して出発」の時間で出発されます。
(この場合、delay も更新されません)

この出庫後の最初の停車でlast_departureが記録されますので、
以降の停車では出発予定時刻の計算と調整が行われるようになります。


== ジッタによる遅延の発生
シミュレーション全体の重さや、Simutransの入れる処理ディレイ(一部ではランダム)
などがあり、実際の列車の出発時刻を厳密に守ることはできません。
そのため、列車はほぼ常に若干遅延のまま運行します。

ですが、計画が適切に設定されている (駅間計画時間が列車の走行性能に対して余裕がある) 
状態であれば、遅延時間が積もって増大していくことはありません。

なお現在は、発車判定の時に使用する現在時刻に対してスラック
(TIMETABLE_SLACK == 1000ticks) を入れていますが、それでもだいたい遅延側に
振れてしまうようです。


--------------------
= 周期的始点発時刻計画の仕組み
--------------------
スケジュール (路線) に対してタイムテーブルの周期時間 (cycle) を指定する
(非0にする) と、始点(スケジュールの先頭)では、出発予定時刻の決定方法が変化します。

始点とはスケジュールの先頭の項目です。これは必ず駅(halt)でなければなりません。
これが中継点(waypoint)の場合は、この機能は動作しません。)

それぞれの列車には始点発時刻のオフセット (offset) を指定できます。
これは始点を出発する時刻の列車ごとのずらし分です。

始点での出発予定時刻は以下のように計算されます。

  go_on = (now / cycle) * cycle + offset
  if (go_on < now)  go_on += cycle

つまり簡単に言えば、
  cycleごとの時刻の中で、時刻がoffsetになった時に出発する
となります。

例:
  cycle=1000で、列車Aがoffset=0、列車Bがoffset=500だったら、
  nowを1000で割った余りが
      0 の時に列車Aが始点から出発
    500 の時に列車Bが始点から出発
  となります。

基準にしているnowはゲーム内の絶対時刻であるため、複数の種別があった場合でも
それらのcycleを揃えておけば、同一の周期内でパターン化して、offsetを指定できます。

例:
  cycle=1000で、普通Aがoffset=0, 普通Bがoffset=500, 優等Aがoffset=300だったら、
  nowを1000で割った余りが
      0 の時に普通Aが始点から出発
    300 の時に優等Aが始点から出発
    500 の時に普通Bが始点から出発
  となります。


--------------------
= 始点発時刻の自動均等設定
--------------------
路線(line)に対して出発時刻の間隔(pitch)を指定してトリガーをかけると、
路線に所属する全ての列車に対して自動的にoffsetを付与することができます。

この時、オフセットの基底時(base)も合わせて指定することができ、これを利用すれば
全ての列車のoffsetを一定量だけずらすといったこともできます。
また、運行障害などによってダイヤが大幅に乱れた場合などに、スジを落として
ダイヤ回復をさせる(編成の運用は変更される)のにも利用できます。

自動均等設定は以下のように行われます。

  トリガーがかかった後、最初に始点に到着した列車:
    base + pitch * n が (now % cycle) を過ぎていてもっとも近い n を選び、
   この値を offset に設定する。

  以降に指定に到着した列車:
    この前に設定した列車の offset に pitch を加える (mod cycle)

全ての列車にoffsetが設定できたら、自動均等設定が終了します。
(以降はoffsetは設定された値のまま運行を繰り返します)


例えば
  cycle = 1000
  pitch = 250
  base = 20
で所属列車が4編成 (A, B, C, D)の時にトリガーがかかったとします。
列車は D, C, A, B の順番に線路上で走行しているとして:

  列車Cがトリガー後最初に始点に到着: この時の now=5680 だとすると
    周期内現時刻 5680%1000 は 680 であり、これに最も近い値を選ぶとn=3、つまり
    C.offset = base+pitch*n = 20 + 250 * 3 = 770

  列車Aが始点に到着:
    A.offset = C.offset + 250 (mod cycle) = 20

  列車Bが始点に到着:
    B.offset = A.offset + 250 (mod cycle) = 270

  列車Dが始点に到着:
    D.offset = B.offset + 250 (mod cycle) = 520

これで一周分のoffsetが自動設定完了です。


--------------------
= 時間の単位と最大値
--------------------
ゲーム内でのシミュレーション時間の単位と、
このパッチの機能でユーザに見える形で表現、入力する時間の単位は異っています。
これは、ゲームデータ量の節約とユーザに数値を扱いやすくさせるためです。

== Simutrans内の時間 (参考; ご存知の方は飛ばしてください)
Simutrans内ではシミュレーション時間の最小単位は、実時間のmsec(ミリ秒)で
管理しています。(これをコード上では tick とか ms とか呼んでいます)
ゲームパラメータ 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ヶ月を表現可能)
ただしタイムテーブル周期やその中でのオフセット時間の指定については、
やはり単位はTTTですが、値域は32bit符号無しです。


--------------------
= 使用方法
--------------------
基本的な(推奨の)使い方の流れは以下のようになります。

  1. 路線を作成する (路線作成と編集)
  2. 列車に路線を適用して走行を開始させる
  3. 列車の運行実績時間の記録を見て、路線に計画時間を設定する (路線の編集)
  4. 適宜、列車の遅延をリセットする (列車の詳細ダイアログ)

ここまでで、個々の列車が、制御された駅間時間によって運行するようになります。
ただしこのままでは、列車と列車のあいだの間隔を制御されたものにはできていません。
↓はオプショナル

  5. 路線に周期時間を設定して列車の間隔調整を行う
     a. 各列車にオフセットを設定する (列車のスケジュール編集ウィンドウ)
     b. 間隔を設定して自動均等設定を行う (路線管理ウィンドウ)

このあとは適宜調整です。

ちなみに簡易タイムテーブル機能は路線化していなくても(列車のスケジュールだけでも)
使用することができます。
ですが、のちのちの増発や調整のためにも路線化されていた方が便利ですし、
自動均等設定を行う場合には、路線化されていることが必須です。

また、各機能には別の組み合わせ方も考えられるかもしれません。

以下では、ゲーム内の各ウィンドウに追加された設定、表示項目、機能を説明します。


----
== 路線・スケジュール編集ウィンドウ
----
停車項目の表示が拡張されています。
スケジュール内の個々の行について (その項目が駅であれば):
{{{
  1) [150] (125) どこか駅 (12,34,0)
}}}
というような表示が行われます。
駅名の前にある二つの括弧内の値はそれぞれ以下のような意味です。

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

  (yyy)  この駅の出発時に計測された実績時間。単位はTTT。
         (前の駅を出発してからこの駅を出発するまでにかかった時間。最新の実績値)
         列車のスケジュール編集ウィンドウで Show RecTime ボタンを
         押下した場合のみ表示されます。
         (路線には表示されません。)
         実績時間が記録されていない場合は表示されません。

ウィンドウ上部には以下の設定フィールドとボタンが追加されています。

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

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

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

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


また、列車のスケジュール編集ウィンドウ (路線ではない) には、さらに
以下のフィールドも追加されます。

Convoy Offset: (数値入力、単位はTTT)
  始点発時刻指定運行を行う時に、この列車の発時刻オフセットを指定します。
  (この値は一つの列車そのものに指定される値であり、
   スケジュール行の選択には関係ありません。)

Show RecTime: (トグルボタン)
  各スケジュール行に列車が実際に走行して記録した実績時間が表示されます。


----
== 時刻表表示
----
開発途上の機能ですが、路線に所属する全ての列車の運行計画を、
時刻表のようなスタイルで表示できます。
将来的に、このウィンドウから種々の設定操作などもできるようになればと
考えています。
これを有効にするには、
{{{
  CFLAGS += -DSCHEDULE_TIMETABLE_TTV
  SCHEDULE_TIMETABLE_TTV=1
}}}
をビルドコンフィグに追加してビルドしてください。

路線管理ウィンドウの中で路線を選択して、"View Timetable" ボタンを押せば
このウィンドウが開きます。

表計算形式の表示で
  ロウはスケジュール上の各駅
  カラムは路線内の各列車
を表示します。
セルに表示される値は、周期内の時間 0 からの各駅の計画発車時刻です。
(ロウにはスケジュールを一周させて、最下行にもう一度、最初の駅を表示しています。)

スケジュールに周期時間が設定されている場合は、各駅での時刻は
この周期時間で割った余りになります。


----
== 列車の詳細情報ダイアログ
----
列車の詳細情報ダイアログには、以下のフィールドが追加されています。

Delay:
  この列車の保持している積算遅延時間が表示されます。

Reset Delay: (ボタン)
  この列車の積算遅延時間をリセットします。


----
== 路線管理ウィンドウ
----
路線管理ウィンドウには以下の設定フィールドとボタンが追加されています。
(路線を選択した場合にのみ有効です。)
一部のフィールドは、路線にタイムテーブル周期 (cycle) が設定されている場合のみ
操作可能です。

Reset Delay All: (ボタン)
  路線に所属する全ての編成の積算遅延時間をリセットします。

Pitch: (ボタンと数値入力、 単位はTTT)
  自動間隔設定のための間隔(pitch)を入力します。
  ボタンを押した場合は、cycleを現在の編成数で割った値が自動計算されます。

Auto Offset: (ボタンと数値入力、単位はTTT)
  右の数値入力には、間隔の基底値(base)を入力します。
  ボタンを押すことで自動オフセット設定を開始します。
  自動オフセット設定が行われている間はボタンは押下されたままの状態になります。
  全ての列車へのオフセットの設定が完了すると、ボタンは元に戻ります。


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

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


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

バグのご報告や機能改善のご提案はウェルカムです。
(ただし充分に対応できる余裕はございませんので対応が遅れましてもご了承ください。)
ご連絡は日本語フォーラム
  Japanese Simutrans Forum
の該当トピックへお願いいたします。
(どうしても個人的に、という場合は、wackdone at gmail.com へメールください)

また以下のような形でお手伝いいただけると助かります。
  * 動作評価、特に複雑な路線を組んだ場合など
  * Windowsなどのプラットフォーム用の実行バイナリ作成、配布と動作確認
  * 使用方法、チュートリアルや応用の説明ページ化や動画化
  * ドキュメントやコード内コメントなどの英語化 (私の英語は偽物英語orz)
  * コードの拡張
これらを経て、ある程度の動作の確認と支持が得られれば、
将来的には本家へ取り込んでもらえるようにしたいと考えています。

なお、みなさんに是非この機能を使ってより深くSimutransを楽しんでいただけたらと
願っております。
この機能を使ったマップ紹介ページや動画などを公開して頂くのは大歓迎です。


--------------------
= 既知の問題点と課題
--------------------
課題: 始点発時刻指定でのダイヤ乱れ
  運行の遅れがたまり過ぎていて、列車の始点への到着が予定されていた始点発時刻を
  過ぎてしまうと、出発が最悪 cycle 分だけ遅れてしまうおそれがあります。
  (当然、続行列車も詰まります。)
  これを自動的に解消する (スジを自動で落とすなど) ロジックとして
  いまいちいいものが見つかりません。
  現実世界でも完全な機械化はされていない (Computer Aided はある) ので、
  そうそう上手くはいかないでしょう。
  今の機能では、(等間隔運転の場合に限って) offsetの自動再設定機能を使えば
  ある程度の回復はできます。
  しかし、現状から計画までのあいだをスムーズにつなぐものではありません。

課題: 時間調整の精度
  Simutransのシミュレーションの仕組みからくる制限ではあるのですが、
  現在 slack==1000msec (==1sec) であっても、なかなか正確な発時が守れません。
  この機能が有効な場合には、convoi の wait_lock にも少し手を入れなければ
  ならないかもしれない。(ただし確実に重くなっていきそうだが)

課題: GUIのセンスが悪い
  この機能を使用しない場合にはうるさくてしょうがないだろうし、
  列車や路線によっても表示させたいものは変わるだろう。
  スケジュール編集画面はタブ化するなり、「表示の設定」でいじれるなりしないと
  いけないかもしれない。

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

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

問題と課題: 中継点の扱い (間隔時間を設定できるようにするか？)
  今は中継点にも値が入力できてしまうが、これを設定すると動作不具合が起きうる。
  とりあえず考えられる案としては
  * 中継点に設定した所要時間も使用する。
  * 停車はしないので遅延時間を再計算するのみ。
  (中継点を通過する時点で余裕が出ていれば、遅延がマイナスに振れて
   次の駅での停車がのびる。遅れていれば逆)
  駅だったところを破壊して中継点になってしまったり、その逆だったりした時に、
  こうしておけばタイムテーブルの引き継ぎがうまくいくと考えられる。

課題: 発-発間時間の分離をするか？
  今の駅発→駅発の時間を  駅発→駅到着 と 駅停車時間 に分けるかどうか。
  (分けたところで、アルゴリズム上はまとめて扱う)
  ダイヤ設定的にはこの方が使いやすくなるだろうが、記憶量 (セーブデータ量も)
  に大きく影響してしまう。

課題: 実績時間の表示
  現在、
  実績時間の表示は列車スケジュール編集で
  タイムテーブルの調整は路線スケジュール編集で
  に分離してしまっており、タイムテーブルの調整がやりにくい。
  どちらも路線スケジュール編集で見られるといいのだが。
  ただし、路線編集側では「どの列車の実績時間を見るのか」という選択が
  必要になってしまう。

夢: ダイアグラム編集
  グラフィカルに編集できるとかっこいいなぁ。。。
  せめて現在のスケジュールを外部とのあいだでCSVなどでimport/exportできるように
  すれば、外部ツールに頼れるだろうか。
  また駅間距離も自動計測(ルーティングロジックを限定的に使って)したい。

課題: いろいろと
  問題・課題はたくさんあれど、
  * バイナリ配布
  * ドキュメント、使用ガイドのわかりやすいものを作る
  * (セーブバージョン番号をまともにするには) 本家へ働きかける
  などが普及のための最優先課題だろうか

