Lethal Company v72 (Build ID: 18916695)時点のQuotaの計算に関する仕様の解析メモです。
Quotaの増加量のランダム性 Quotaの増加量のランダム性は、TimeOfDay.quotaVariables.randomizerCurveで定義される、右肩上がりのランダマイザ曲線によってもたらされます。
このランダマイザ曲線は、以下の制御点を持つエルミート曲線です。
x y in_tangent out_tangent 0 -0.5030289 7.455404 7.455404 0.117235 -0.1301773 0.5548811 0.5548811 0.8803625 0.1534421 0.5221589 0.5221589 1 0.5030365 7.051469 7.051469 Unity 2022.3.9f1のAnimationCurveが使われています。 preWrapModeおよびpostWrapModeは、ClampForeverに設定されています。 これにより、[0, 1]の外側の評価値は、端点の値になります。
https://docs.unity3d.com/2022.3/Documentation/ScriptReference/WrapMode.ClampForever.html ランダマイザ曲線の描画スクリプト(Python) Details from dataclasses import dataclass from typing import List import bisect import numpy as np import matplotlib.pyplot as plt from matplotlib.ticker import MultipleLocator # ---- Unity AnimationCurve Keyframes ---- # pre_wrap, post_wrap: ClampForever KEYFRAMES_RAW = [ {"time": 0.0, "value": -0.5030289, "inTangent": 7.455404, "outTangent": 7.455404}, {"time": 0.117235, "value": -0.1301773, "inTangent": 0.5548811, "outTangent": 0.5548811}, {"time": 0.8803625, "value": 0.1534421, "inTangent": 0.5221589, "outTangent": 0.5221589}, {"time": 1.0, "value": 0.5030365, "inTangent": 7.051469, "outTangent": 7.051469}, ] @dataclass class Key: time: float value: float inTangent: float outTangent: float # ---- Unity-style Hermite evaluator (non-weighted) ---- class Curve: def __init__(self, keys: List[Key]): self.keys = sorted(keys, key=lambda k: k.time) def evaluate(self, t: float) -> float: if not self.keys: return 0.0 if len(self.keys) == 1: return self.keys[0].value times = [k.time for k in self.keys] # ClampForever: 範囲外は端の値を返す if t <= self.keys[0].time: return self.keys[0].value if t >= self.keys[-1].time: return self.keys[-1].value i = bisect.bisect_right(times, t) - 1 k0 = self.keys[i] k1 = self.keys[i + 1] t0, t1 = k0.time, k1.time y0, y1 = k0.value, k1.value dx = t1 - t0 if dx == 0.0: return y1 u = (t - t0) / dx m0 = k0.outTangent m1 = k1.inTangent h00 = 2*u**3 - 3*u**2 + 1 h10 = u**3 - 2*u**2 + u h01 = -2*u**3 + 3*u**2 h11 = u**3 - u**2 return h00*y0 + h10*(dx*m0) + h01*y1 + h11*(dx*m1) def main(): # Build curve curve = Curve([Key(**k) for k in KEYFRAMES_RAW]) # Sample and plot # 右に5%の外挿を含めてサンプリング t_min = KEYFRAMES_RAW[0]["time"] t_max = KEYFRAMES_RAW[-1]["time"] t_range = t_max - t_min ts = np.linspace(t_min, t_max + t_range * 0.05, 500) ys = np.array([curve.evaluate(t) for t in ts]) plt.figure( figsize=(16, 9), dpi=300, facecolor='white', edgecolor='black' ) plt.plot(ts, ys) plt.scatter([k["time"] for k in KEYFRAMES_RAW], [k["value"] for k in KEYFRAMES_RAW], label="Keyframes") plt.xlabel("Input") plt.ylabel("Output") plt.legend() plt.title("Quota randomizer curve") plt.grid(True, which='both', axis='both') plt.gca().xaxis.set_major_locator(MultipleLocator(0.05)) plt.gca().yaxis.set_major_locator(MultipleLocator(0.1)) ts_expect = np.linspace(t_min, t_max, 1000) ys_expect = np.array([curve.evaluate(t) for t in ts_expect]) expected_value = ys_expect.mean() print(f"期待値: {expected_value:.4f}") # 特別なX値でのY値を取得し、グラフに表示 special_xs = [0.7865, 1.012] for x in special_xs: y = curve.evaluate(x) plt.plot(x, y, 'ro') # 赤丸でマーク plt.text(x, y, f"({x:.4f}, {y:.4f})", color='red', fontsize=12, ha='right', va='bottom') # 制限値付きの期待値の計算 ts_expect = np.linspace(t_min, x, 1000) ys_expect = np.array([curve.evaluate(t) for t in ts_expect]) expected_value = ys_expect.mean() print(f"制限付き期待値(x <= {x}): {expected_value:.4f}") # Y=0となるtを探索し、同様にプロット zero_crossings: list[float] = [] for i in range(len(ts)-1): if ys[i] * ys[i+1] < 0: # 線形補間でゼロ点近似 t0, t1 = ts[i], ts[i+1] y0, y1 = ys[i], ys[i+1] t_zero = t0 - y0 * (t1 - t0) / (y1 - y0) zero_crossings.append(t_zero) for x in zero_crossings: y = 0.0 plt.plot(x, y, 'ro') plt.text(x, y, f"({x:.4f}, 0.0000)", color='red', fontsize=12, ha='right', va='bottom') plt.savefig("quota_randomizer_curve.png") if __name__ == "__main__": main() LuckとQuotaの関係 Luckが大きいほどQuotaは上がりにくくなり、Luckが小さいほどQuotaは上がりやすくなります。
...