Skip to content

timeline #4

Description

@torabit

Why

デバイス時刻の校正と wrap 処理をここに閉じる。この 2 つが狂うと表示される F 数が丸ごと無意味になる。純粋なロジックとして切り出し、合成データでテストできる形にする。

What

src/timeline.rs を作る。hidapi に依存させない。

pub struct InputEvent {
    pub kind: EventKind,
    pub target: Target,
    pub at_us: u64,
    pub gap_before: bool,
}

pub enum EventKind { Press, Release }

pub enum Target { Button(Buttons), Dpad(Direction), Stick(Direction) }

pub struct Timeline { /* 内部状態 */ }

impl Timeline {
    pub fn new(device: Device, stick_deadzone: f32, stick_hysteresis_deg: f32) -> Self;
    pub fn push(&mut self, state: PadState, host_qpc_us: u64) -> Vec<InputEvent>;
    pub fn now_us(&self) -> u64;
    pub fn scale_us_per_tick(&self) -> f64;
    pub fn scale_warning(&self) -> Option<String>;
}

やることは 5 つ。

1 つ目はスケール校正。デバイス時刻の 1 tick が何µs かを決める。公称値は DualSense が 0.33、DualShock 4 が 5.33。機種と FW によって DualSense が 1.0 になる分岐が SDL に存在するため、決め打ちしない。最初の 2 秒 (host_qpc_us で判定) のレポートを溜め、host_qpc_us の差を device_ts の差で割って推定する。推定が終わるまでは公称値を使う。推定値が公称値から 20% 以上外れたら scale_warning に文言を入れる。

2 つ目は wrap 処理。device_ts は DualSense が 32bit、DualShock 4 が 16bit で回る。5.33µs × 65536 は約 349ms なので、DualShock 4 では無入力が 349ms を超えると前回値との比較だけでは wrap 回数が決まらない。host_qpc_us の経過時間から期待される経過 tick 数を出し、それに最も近い wrap 回数を選ぶ。

3 つ目はイベント化。前回の PadState と比較し、立ったビットを Press、落ちたビットを Release にする。dpad は方向が変わったら前の方向の Release と新しい方向の Press を出す。左スティックは 8 方向に量子化してから同じ扱いにする。量子化は中心からの正規化距離が stick_deadzone を割ったらニュートラルとする。

量子化だけだと方向が境界で揺れる。8 方向の境界にスティックを置くと、量子化結果が隣り合う 2 方向を往復し、Release と Press の対が延々と出る。スティックはアナログなので、境界の上に止めるのは難しくない。

方向が変わるのは、新しい方向の中心軸から stick_hysteresis_deg 以内に入ったときだけにする。既定は 15 度。それより手前では前の方向を保つ。ニュートラルへの復帰は stick_deadzone だけで決める。

4 つ目は現在時刻の公開。now_us は最後に push したレポートの at_us を返す。イベントを 1 件も出さなかったレポートでも進む。押しっぱなしの持続時間を数えるのと、無入力が続いたことを検出するのに使う。どちらもイベントが来ないので、イベントの列だけでは時刻が進まない。

5 つ目は欠落の検出。seq が 1 つ進んでいなければ gap_before を true にする。DualSense は 8bit、DualShock 4 は 6bit で回るので、それぞれの幅で比較する。欠落を補間しない。

at_us は最初のレポートを 0 とした連続値にする。

ゴール

以下がそれぞれ独立にテストされている。

  • スケール校正が公称値どおりの合成データで公称値を推定する
  • スケール校正が 3 倍ずれた合成データで scale_warning を返す
  • DualShock 4 の 16bit wrap が 1 回、2 回、5 回の各ケースで正しい at_us になる
  • 無入力 400ms を挟んだ wrap が host_qpc_us から解決される
  • seq の欠落が gap_before に出る
  • dpad の方向変更が Release と Press の対で出る
  • 左スティックが stick_deadzone を割ったときにニュートラル扱いになる
  • 8 方向の境界の上でスティックを微動させた合成データで、方向が 1 度も変わらない
  • 境界から stick_hysteresis_deg を超えて踏み込んだ合成データで、方向が変わる
  • イベントを 1 件も出さないレポートを続けて push したあとでも now_us が進む

あわせて以下を満たす。

  • timeline.rs が hidapi に依存していない
  • cargo coupling --check --min-grade=B --max-circular=0 ./src が通る

分解: #1 (comment) / Task 3 / e29acba562d5

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions