# 機能設計書 60-アナログ入力

## 概要

本ドキュメントは、K-ShootMania（KSM）におけるアナログ入力処理機能の設計を記述する。アナログ入力は、レーザー操作のために6つの入力モード（キーボード/PowerMate/マウス/アナログスティック/スライダー/ジョイスティック軸）を提供し、各モードに応じたカーソル移動量の計算とレーザー判定を行う。

### 本機能の処理概要

アナログ入力処理は、analoginput変数の値に応じて異なる入力デバイスからレーザー操作のカーソル移動量を取得し、analogpos配列を通じてレーザー判定に使用する仮想カーソル位置を更新する。キーボード入力（離散的）とデバイス入力（連続的）で異なるカーソル移動ロジックが実装されている。

**業務上の目的・背景**：KSMのレーザーノートはアナログ的な左右操作を必要とするため、キーボードの離散入力だけでなく、マウス・ジョイスティック・専用コントローラー（PowerMate等）による連続入力をサポートする。

**機能の利用シーン**：プレイ画面でのレーザーノート判定、選曲画面での曲リストスクロール、スプラッシュ画面でのHiSpeed変更に使用される。

**主要な処理内容**：
1. analoginput=0（キーボード）: keystator配列からレーザーL/Rキー入力を読み取り、固定速度でanalogposを更新
2. analoginput=1（PowerMate）: HSPのonkeyイベント割り込みでレーザーキーの押下を検出し、analogposを更新
3. analoginput=2（マウスX/Y座標）: ginfo_mx/ginfo_myのマウス座標変化量からカーソル移動量を計算
4. analoginput=3（アナログスティック）: joyGetPosExのX/Y軸（joystat2.2-3）からカーソル移動量を計算
5. analoginput=4（スライダー/ダイヤル）: joyGetPosExのZ軸（joystat2.4-5）の差分からカーソル移動量を計算
6. analoginput=5（ジョイスティックX/Y座標）: joyGetPosExのX/Y軸（joystat2.2-3）の差分からカーソル移動量を計算

**関連システム・外部連携**：Win32 Multimedia API（joyGetPosEx）、HSP ginfo関数（マウス座標）、HSP onkeyイベント。

**権限による制御**：特になし。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 3 | 曲選択画面 | 補助機能 | アナログデバイスによる曲リストスクロール・難易度切替 |
| 5 | ゲームプレイ画面 | 主機能 | レーザーノート判定のためのアナログカーソル移動 |

## 機能種別

入力処理 / デバイスインターフェース

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| analoginput | int | Yes | アナログ入力モード（0=キーボード, 1=PowerMate, 2=マウス, 3=アナログスティック, 4=スライダー, 5=ジョイスティック軸） | 0-5 |
| analogmode | int | Yes | レーザー表示モード（0=mode_on, 1=OFF, 2=AUTO, 3=HIDE） | 0-3 |
| analogsw | int | Yes | アナログ入力のL/R入替フラグ | 0 or 1 |
| msen | int | Yes | マウス感度（デフォルト50） | config.ini設定値 |
| mx | int | Yes | マウスX反転フラグ（0=通常, 1=反転） | 0 or 1 |
| my | int | Yes | マウスY反転フラグ（0=通常, 1=反転） | 0 or 1 |

### 入力データソース

- config.ini: analoginput（laserinput=）、analogmode（laser_mode=）、msen（mouse_sensitivity=）の設定
- キーボード: keystator.6-9（レーザーL/Rキー）
- マウス: ginfo_mx/ginfo_my（HSP関数、マウス座標）
- ジョイスティック: joyGetPosExのjoystat2配列（軸データ）
- PowerMate: HSP onkeyイベントの割り込みハンドラ（*keshu）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| analogpos | int[2] | レーザーL/R仮想カーソル位置（0-1000の範囲） |
| analognow | int[2] | 現在のレーザーノート位置（譜面データから算出、-1=非アクティブ） |
| stickstat | int[2] | アナログデバイスの方向状態（-1=左/下, 0=中立, 1=右/上） |
| sliderp | int[2] | スライダー前回値（差分計算用） |

### 出力先

レーザー判定ロジック（play_laser_note.hsp）、スコア計算、エフェクト制御

## 処理フロー

### 処理シーケンス

```
1. 初期化フェーズ（play_init.hsp）
   a. analoginput=2(マウス)かつanalogmode=mode_on: マウスカーソルを画面中央(defx,defy)に移動
   b. analoginput=4(スライダー): sliderp配列を初期化（現在のスライダー位置を記録）

2. 毎フレーム処理（play_laser_note.hsp 209行目以降）
   a. analogmode=mode_onかつゲージ条件を満たす場合のみ実行

   [analoginput=4 スライダーモード]
   - joyGetPosExのjoystat2.(4+cnt0^analogsw)から現在のスライダー値を取得
   - 前回値(sliderp)との差分を計算、65536超えの場合はラップアラウンド補正
   - 差分にmsen/400を乗算して移動量stat1を算出
   - |stat1|<25の場合はstickstat=0（デッドゾーン）

   [analoginput=5 ジョイスティック軸モード]
   - joyGetPosExのjoystat2.(2+cnt0^analogsw)の差分を計算
   - スライダーモードと同様の処理（ラップアラウンド補正、msen/400乗算、デッドゾーン25）

   [analoginput=3 アナログスティックモード]
   - joyGetPosExのjoystat2.(2+cnt0^analogsw*2)-32768（中心補正）
   - |stat1|<12000はデッドゾーン
   - 12000超過分を速度に変換（最大18000*t_tp/16/300）

   [analoginput=2 マウスモード]
   - (ginfo_mx-defx)*2*(mx*2-1)でマウス移動量を計算
   - msen/10を乗算
   - |stat2|<10はデッドゾーン
   - 30msごとにマウスカーソルを画面中央に戻す

   [analoginput=0 キーボードモード]
   - keystator.6/7（レーザーL左/右）からanalogposを更新
   - 固定速度: 近い場合(t_tp)*400/1000、遠い場合(t_tp)*1350/1000

   [analoginput=1 PowerMateモード]
   - *keshuイベントハンドラ（kshootmania.hsp 1836行目以降）
   - onkeyイベントでレーザーキー押下を検出、40単位でanalogposを更新

3. カーソル位置更新（共通処理）
   a. enter_key=1の場合は移動量を0に（一時停止中は入力無効）
   b. アナログ判定ロジック: analognowとanalogposの差分からダメージ/OK判定
   c. analoganj配列の折り返しポイント判定
```

### フローチャート

```mermaid
flowchart TD
    A[play_laser_note処理] --> B{analogmode=mode_on?}
    B -->|Yes| C{analoginput値分岐}
    B -->|No| Z[キーボード入力のみ]
    C -->|0| D[キーボード: keystator.6-9]
    C -->|1| E[PowerMate: *keshuイベント]
    C -->|2| F[マウス: ginfo_mx/my]
    C -->|3| G[アナログスティック: joystat2.2-3]
    C -->|4| H[スライダー: joystat2.4-5]
    C -->|5| I[ジョイスティック軸: joystat2.2-3差分]
    D --> J[analogpos更新]
    E --> J
    F --> K[マウスカーソルリセット]
    K --> J
    G --> J
    H --> J
    I --> J
    J --> L[レーザー判定]
    L --> M[スコア更新]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-60-1 | モード条件 | analogmode=mode_on かつ (effratetype<=0かつiscourse=0 または gauge/1000>0) の場合のみアナログ入力有効 | play_laser_note.hsp 207行目 |
| BR-60-2 | スライダーデッドゾーン | |stat1|<25の場合はstickstat=0（移動なし） | analoginput=4, 5 |
| BR-60-3 | アナログスティックデッドゾーン | |stat1|<12000の場合はstickstat=0 | analoginput=3 |
| BR-60-4 | マウスデッドゾーン | |stat2|<10の場合はstickstat=0 | analoginput=2 |
| BR-60-5 | マウスカーソルリセット | ts3>30(30ms間隔)でマウスカーソルを画面中央(defx,defy)に戻す | analoginput=2 |
| BR-60-6 | PowerMate割り込み | onkey 1でHSPキー割り込みを有効化、*keshuハンドラで処理 | analoginput=1 |
| BR-60-7 | キーボードカーソル速度 | 近距離: (t_tp)*400/1000、遠距離(>50): (t_tp)*1350/1000 | analoginput=0 |
| BR-60-8 | ラップアラウンド補正 | |差分|>32768の場合、sgn(差分)*65536を減算 | analoginput=4, 5 |
| BR-60-9 | 一時停止中の入力無効 | enter_key=1の場合はstat1=0（移動量ゼロ） | 全analoginputモード |
| BR-60-10 | L/R入替 | analogsw=1の場合、cnt0^analogswでL/Rのデバイス軸を入替 | analoginput=2,3,4,5 |

### 計算ロジック

- **スライダー移動量**: `stat1 = (slider - sliderp) * msen / 400`（ラップアラウンド補正あり）
- **アナログスティック移動量**: `stat1 = min(sgn(v)*(|v|-12000)*t_tp/16/300, 18000*t_tp/16/300)` （v=生値-32768、|v|>12000の場合）
- **マウス移動量**: `stat1 = round((ginfo_mx - defx) * 2 * (mx*2-1) * msen / 10)`
- **キーボードカーソル速度**: 近距離 `(t_tp)*400/1000`、遠距離 `(t_tp)*1350/1000`

## データベース操作仕様

本機能はデータベースを使用しない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | デバイス未接続 | ジョイスティック/スライダーが接続されていない | stickstat=0で入力なしとして処理 |
| - | PowerMateイベント欠損 | onkeyイベントが発生しない | キーボードフォールバックなし（入力待ち） |

### リトライ仕様

不要。毎フレームポーリング（マウス/ジョイスティック）またはイベント駆動（PowerMate）。

## トランザクション仕様

不要。

## パフォーマンス要件

- 毎フレーム（約7ms間隔）でデバイス状態を読み取り
- マウスカーソルリセットは30ms間隔（ts3>30条件）
- PowerMateはイベント駆動のため低遅延

## セキュリティ考慮事項

特になし。

## 備考

- analoginputの値とオプション画面での表示名:
  - 0: キーボードプレイ（処理重め）
  - 1: PowerMate（足で操作）
  - 2: 専用コントローラー（マウスX/Y座標）
  - 3: アナログスティック
  - 4: 専用コントローラー（ダイヤル/スライダー軸）
  - 5: ジョイスティックX/Y座標(-127～127)
- defx/defyは画面中央座標（ginfo_dispx/2, ginfo_dispy/2）
- msenのデフォルト値は50
- analogmodedef: 0=ON(表示あり/判定あり), 1=OFF(表示あり/判定なし), 2=AUTO(表示あり/オートプレイ), 3=HIDE(表示なし/判定なし)

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | kshootmania.hsp | `kshootmania.hsp` | analoginput/analogmode/msen等の初期値とconfig.ini読み込み |
| 1-2 | play_laser_note.hsp | `src/scene/play/play_laser_note.hsp` | analogpos/analognow/canalognow等のレーザーカーソル変数 |

**読解のコツ**: analoginputの値は0-5でオプション画面の巡回順序（0->4->2->1->3->5）とは異なる。analogmodeは実行時にanalogmodedefからコピーされる。

- **kshootmania.hsp 287行目**: `analoginput=0` デフォルト値
- **kshootmania.hsp 290行目**: `analogmode=1` デフォルト値
- **kshootmania.hsp 332行目**: `msen=50` デフォルト感度
- **kshootmania.hsp 71-72行目**: `defx=ginfo_dispx/2`, `defy=ginfo_dispy/2` 画面中央定義
- **play_laser_note.hsp 23-27行目**: analognow/panalognow/canalognow等のレーザーカーソル変数定義

#### Step 2: 初期化処理

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | play_init.hsp | `src/scene/play/play_init.hsp` | アナログ入力の初期化 |

- **706-720行目**: マウスカーソル初期位置設定（analoginput=2）とスライダー初期値取得（analoginput=4）

#### Step 3: アナログデバイス入力処理（プレイ画面）

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | play_laser_note.hsp | `src/scene/play/play_laser_note.hsp` | 6モードのアナログカーソル移動処理 |

- **207-209行目**: analogmode=mode_on条件チェック、analoginput=4/5/3/2の分岐開始
- **215-228行目**: analoginput=4（スライダー）- joystat2.4-5差分、msen/400乗算
- **229-242行目**: analoginput=5（ジョイスティック軸）- joystat2.2-3差分、msen/400乗算
- **243-257行目**: analoginput=3（アナログスティック）- 32768中心補正、デッドゾーン12000
- **258-268行目**: analoginput=2（マウス）- ginfo_mx/my変化量、msen/10乗算
- **339-342行目**: マウスカーソルリセット（30ms間隔、defx/defy）
- **345-368行目**: analoginput=0（キーボード）- keystator.6/7による離散カーソル移動

#### Step 4: PowerMateイベントハンドラ

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | kshootmania.hsp | `kshootmania.hsp` | *keshuイベントハンドラ |
| 4-2 | play_update.hsp | `src/scene/play/play_update.hsp` | onkey有効化 |

- **play_update.hsp 234-236行目**: `if(analoginput=1){ keshucnt=0: onkey 1 }` - PowerMateイベント有効化
- **kshootmania.hsp 1508-1518行目**: `*keshu` - onkeyイベントハンドラ、analoginput=1の場合のみ処理
- **kshootmania.hsp 1836-1870行目**: プレイ中のPowerMateレーザー入力処理（analogpos+-40）

#### Step 5: オプション設定

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | option.hsp | `src/scene/option.hsp` | アナログ入力モード設定UI |

- **option.hsp 618-623行目**: analoginput値に対応する表示名
- **option.hsp 271-280行目**: 左方向切替（4->0->3->1->2->5の逆順）
- **option.hsp 422-431行目**: 右方向切替（0->4->2->1->3->5の順）

### プログラム呼び出し階層図

```
play_init
    |
    +-- analoginput=2: mouse defx,defy (マウス中央配置)
    +-- analoginput=4: sliderp初期化 (スライダー初期位置)
    |
play_update (メインループ)
    |
    +-- analoginput=1: onkey 1 (PowerMateイベント有効化)
    |       +-- *keshu (kshootmania.hsp)
    |               +-- wparam=key.(6+cnt^analogsw*2) -> analogpos更新
    |
    +-- play_laser_note (レーザーノート処理)
            |
            +-- analoginput=4: joyGetPosEx -> joystat2.4-5 -> スライダー差分
            +-- analoginput=5: joyGetPosEx -> joystat2.2-3 -> 軸差分
            +-- analoginput=3: joyGetPosEx -> joystat2.2-3 -> スティック位置
            +-- analoginput=2: ginfo_mx/my -> マウス移動量
            +-- analoginput=0: keystator.6-9 -> キーボード離散入力
            |
            +-- analogpos更新 -> レーザー判定 -> スコア計算
```

### データフロー図

```
[入力]                             [処理]                                [出力]

キーボード (analoginput=0)
  keystator.6-9 ----------------> 固定速度カーソル移動 ----------------> analogpos[2]
                                                                            |
PowerMate (analoginput=1)                                                   |
  onkeyイベント -----------------> *keshu -> +-40単位移動 --------------> analogpos[2]
                                                                            |
マウス (analoginput=2)                                                      |
  ginfo_mx/my ------------------> (pos-defx/y)*msen/10 ----------------> analogpos[2]
  mouse defx,defy (30ms周期)                                                |
                                                                            |
アナログスティック (analoginput=3)                                          |
  joystat2.2-3 -----------------> 32768中心補正 -> デッドゾーン12000 --> analogpos[2]
                                                                            |
スライダー (analoginput=4)                                                  |
  joystat2.4-5 -----------------> 前回値差分 * msen/400 ---------------> analogpos[2]
                                                                            |
ジョイスティック軸 (analoginput=5)                                          |
  joystat2.2-3 -----------------> 前回値差分 * msen/400 ---------------> analogpos[2]
                                                                            |
analogpos + analognow ----------> レーザー判定ロジック ----------------> スコア・ゲージ
                                    +-> anadamage (ダメージ値)
                                    +-> analoganj (折り返しポイント判定)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| play_laser_note.hsp | `src/scene/play/play_laser_note.hsp` | ソース | アナログ入力処理メイン（6モード分岐・レーザー判定） |
| kshootmania.hsp | `kshootmania.hsp` | ソース | *keshuイベントハンドラ、初期値定義、config.ini読み込み |
| play_init.hsp | `src/scene/play/play_init.hsp` | ソース | マウス/スライダー初期化 |
| play_update.hsp | `src/scene/play/play_update.hsp` | ソース | PowerMate onkey有効化 |
| play_splash_screen.hsp | `src/scene/play/play_splash_screen.hsp` | ソース | スプラッシュ画面でのアナログ入力（HiSpeed変更） |
| option.hsp | `src/scene/option.hsp` | ソース | アナログ入力モード設定UI |
| select.hsp | `src/scene/select.hsp` | ソース | 選曲画面でのアナログ入力（スクロール・難易度切替） |
| config.ini | `config.ini` | 設定 | analoginput(laserinput=)、analogmode(laser_mode=)、msen(mouse_sensitivity=) |
