【マイレース開発記録(13)】finish_bonus調整地獄
スピードだけでは「レース」にならない
マイレースのレースロジックが一通り動き始めたころ、
私はある違和感に気づきました。
レースの順位が、ほとんどスピードだけで決まってしまうのです。
マイレースでは、各レーンの速さを次のようなロジックで計算しています。
Python
def speed(natal_lon, transit_lon):
return 1.0 + (1 - angle_diff(natal_lon, transit_lon) / 180) * 0.4
出生図(natal)と当日の天体(transit)の角度差から、その日の「外側の流れ」をスピードとして算出する仕組みです。
ただ、この計算だけだと、問題がありました。
順位がほぼ固定されてしまうのです。
スピードが速いレーンは常に上位、遅いレーンは常に下位になりやすい。
もちろん占星術的にはそれも意味がありますが、
「レースとして見て楽しいか」という観点では少し物足りないと感じました。
ジャンプを順位に影響させたい
マイレースでは、うさぎの動きに
- 走る速さ(外側の流れ)
- ジャンプ(内側のリズム)
という2つの要素があります。
ジャンプは次のようにフェーズから計算しています。
Python
def calc_phases(birthday: date, target: date):
days = (target - birthday).days
return {
"moon": calc_phase(days, 29.5),
"mars": calc_phase(days, 687),
}
- 月のリズム
- 火星のリズム
この2つを使って、ジャンプの高さを変えています。
しかし当初は、このジャンプは完全に見た目だけの演出でした。
レース結果には影響していません。
そこで考えたのが、
ジャンプのタイミングを、ゴールタイムに少しだけ影響させる
という仕組みでした。
finish_bonus の導入
そこで追加したのが、次の関数です。
Python
def speed(natal_lon, transit_lon):
return 1.0 + (1 - angle_diff(natal_lon, transit_lon) / 180) * 0.4
出生図(natal)と当日の天体(transit)の角度差から、その日の「外側の流れ」をスピードとして算出する仕組みです。
ただ、この計算だけだと、問題がありました。
順位がほぼ固定されてしまうのです。
スピードが速いレーンは常に上位、遅いレーンは常に下位になりやすい。
もちろん占星術的にはそれも意味がありますが、「レースとして見て楽しいか」という観点では少し物足りないと感じました。
ジャンプを順位に影響させたい
マイレースでは、うさぎの動きに
- 走る速さ(外側の流れ)
- ジャンプ(内側のリズム)
という2つの要素があります。
ジャンプは次のようにフェーズから計算しています。
Python
def calc_phases(birthday: date, target: date):
days = (target - birthday).days
return {
"moon": calc_phase(days, 29.5),
"mars": calc_phase(days, 687),
}
- 月のリズム
- 火星のリズム
この2つを使って、ジャンプの高さを変えています。
しかし当初は、このジャンプは完全に見た目だけの演出でした。
レース結果には影響していません。
そこで考えたのが、
ジャンプのタイミングを、ゴールタイムに少しだけ影響させる
という仕組みでした。
finish_bonus の導入
そこで追加したのが、次の関数です。
Python
def finish_bonus(phase: float) -> float:
rhythm = (phase - 0.5) * 2 # -1.0〜+1.0
return - rhythm * 0.15 # 最大±0.15秒
このロジックでは、
- フェーズが高い(リズムが良い)
- フェーズが低い(リズムが落ち着いている)
といった状態によって、
ゴールタイムに最大 ±0.15秒 の補正をかけます。
そして実際のゴールタイムは、
Python
base_time = 1.0 / spd
finish_times[key] = base_time + finish_bonus(phase)
という形で計算しています。
つまり、
- スピードがベースの順位
- リズムが微調整
という構造です。
調整は、思ったより難しかった
ここで問題が起きました。
補正値が大きすぎると、
- スピードが遅いレーンが突然1位になる
- レース結果が毎回バラバラになる
逆に小さすぎると、
- ほとんど順位が変わらない
- ジャンプの意味がなくなる
この調整が、予想以上に難しかったのです。
0.3
0.2
0.1
0.05
何度も数値を変えながら、レースを繰り返し眺めることになりました。
AIとの「壁打ち」
このあたりの調整は、AIとの対話がかなり役立ちました。
私はよく、
- 今のロジックはどういう振る舞いになるのか
- 数式としてどういう意味になるのか
- バランスはどこにあるのか
といったことを、言葉で説明しながら整理します。
AIにコードを書いてもらうというより、自分の考えを説明するための相手として使うことが多いです。
特に私の場合、頭の中だけで考えていると整理が止まってしまうことがあります。
言葉にして説明することで、
「あ、ここがズレているのか」
と気づくことがよくあります。
レースとして「気持ちいい」ライン
最終的に、
Python
return - rhythm * 0.15
という値に落ち着きました。
このくらいだと、
- 基本の順位はスピードが決める
- でもジャンプ次第で入れ替わることもある
という、
「レースとしてちょうど気持ちいい揺らぎ」になります。
完全なランダムにはしない
もうひとつ意識したのは、
完全なランダムにはしない
ということです。
マイレースでは、
- 生年月日
- 眺めたい日
- カテゴリ
をもとに、結果が決まります。
つまり同じ条件なら、同じレース結果になるという設計です。
これは占いとしての一貫性を保つためでもあり、アプリとしての再現性を守るためでもあります。
小さな補正が、体験を変える
finish_bonus は、コードとしてはとても小さな関数です。
でもこの補正を入れたことで、
- レースが少しドラマチックになり
- ジャンプが意味を持ち
- 見ていて楽しい動きになりました
ロジックとしてはほんの数行ですが、体験を変える数行になったと思っています。
次回予告
次回は、
として、開発中に「これは助かった」と思ったデバッグの仕組みについて書いていきます。


