# 機能設計書 59-ジョイスティック入力

## 概要

本ドキュメントは、K-ShootMania（KSM）におけるジョイスティック（ゲームパッド）入力処理機能の設計を記述する。Win32 Multimedia API（winmm.dll）を通じてジョイスティックの接続検出・ボタン状態取得を行い、2セットのジョイスティックキーバインド（joykey配列）を通じてゲーム操作に変換する。

### 本機能の処理概要

ジョイスティック入力処理は、接続されたジョイスティックデバイスのボタン状態をポーリングし、joykey配列（ボタン番号マッピング）を通じてBT/FX/レーザー/Start/Backの各操作に変換する。最大32ボタンをサポートし、複数のジョイスティックデバイスに対応する。

**業務上の目的・背景**：音楽ゲーム専用コントローラーやゲームパッドを接続してプレイするために必要な入力機能である。キーボード入力と統合することで、多様なデバイスでのプレイを実現する。

**機能の利用シーン**：ゲームパッドや専用コントローラーを使用するプレイ画面・選曲画面・タイトル画面・オプション画面等の全画面で使用される。オプション画面ではジョイスティックのボタン割り当て設定も行う。

**主要な処理内容**：
1. JStickIni: ジョイスティックドライバ初期化（接続デバイス数取得）
2. JStickSearchID: 接続されたジョイスティックIDの検出
3. *joyinit: joystat/joystatp配列の初期化
4. *getjoystat: 毎フレームのジョイスティックボタン状態ポーリング（joyGetPosEx API）
5. processKeyInput内のジョイスティック入力変換: joystat/joykey配列からkeystat配列への変換
6. getjoykeystat: 個別キーのキーボード+ジョイスティック統合判定

**関連システム・外部連携**：Win32 Multimedia API（winmm.dll）のjoyGetPosEx/joyGetNumDevs/joyGetDevCapsA。

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

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 1 | タイトル画面 | 補助機能 | JStickSearchIDによるジョイスティック検出、ボタン入力による画面遷移 |
| 3 | 曲選択画面 | 補助機能 | ジョイスティックボタンによる曲選択・難易度切替 |
| 5 | ゲームプレイ画面 | 主機能 | BT/FXノート判定、レーザー操作のジョイスティック入力 |
| 6 | リザルト画面 | 補助機能 | Start/Backボタンによる遷移操作 |
| 7 | コースリザルト画面 | 補助機能 | Start/Backボタンによる遷移操作 |
| 8 | オプション設定画面 | 設定機能 | ジョイスティックボタン割り当て設定 |

## 機能種別

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

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| joykey | int[2][14] | Yes | ジョイスティックキーバインド2セット（デバイスID*32+ボタン番号、未使用は-1） | config.ini設定値 |
| joylist | int[] | Yes | 検出されたジョイスティックデバイスIDリスト | JStickSearchIDで自動取得 |
| length_joylist | int | Yes | 検出されたジョイスティック数 | 0以上 |

### 入力データソース

- config.ini: ジョイスティックキーバインド設定（joy=/joy2=行）
- Win32 Multimedia API: joyGetPosEx（ボタン状態取得）、joyGetNumDevs（デバイス数取得）
- third_party/jstick.as: ジョイスティックAPI HSPラッパーモジュール

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| joystat | int[N][32] | ジョイスティックデバイスごとの32ボタン状態配列（1=押下, 0=非押下） |
| joystatp | int[N][32] | 前フレームのジョイスティックボタン状態（エッジ検出用） |
| keystat[2-5][0-10] | int | processKeyInput内でジョイスティック入力がマッピングされた状態 |

### 出力先

keystat/keystator配列を通じてゲームロジック各モジュールへ

## 処理フロー

### 処理シーケンス

```
1. 初期化フェーズ（タイトル画面/オプション画面起動時）
   a. JStickIni: winmm.dllのjoyGetNumDevsでドライバのジョイスティック最大数を取得
   b. JStickSearchID 0,joylist: 接続中のジョイスティックIDを検出 -> joylist配列
   c. length_joylist = stat（検出数）
   d. gosub *joyinit: joystat/joystatp配列をjoylist最大ID+1 x 32で初期化

2. 毎フレームポーリング（*getjoystatサブルーチン）
   a. joystatp = joystat（前フレームの状態保存）
   b. joystat配列を再初期化
   c. repeat length_joylist: 各ジョイスティックについてjoyGetPosExで状態取得
   d. joydata.8のビットフィールド（32ボタン分）をjoystat配列に展開

3. processKeyInput内のジョイスティック変換（play_key_input.hsp 259-270行目）
   a. repeat 2（joykey 2セット分）
   b. repeat 11（BT4+FX2+レーザー4+FXコンボ）
   c. joykey.cnt2.cnt/32 でデバイスID、joykey.cnt2.cnt\32 でボタン番号を取得
   d. joystat.(デバイスID).(ボタン番号) -> keystat.(cnt2+2).cnt にマッピング
   e. FXコンボキー（keystat.*.10=1）の場合、keystat.6.4/5=1

4. getjoykeystat関数（kshootmania.hsp 2219-2247行目）
   a. キーボード入力（key/key2）とジョイスティック入力（joykey 2セット）をOR結合
   b. Start/Back/F11キーはkey配列から除外（コメントアウト）、key2のみ有効
```

### フローチャート

```mermaid
flowchart TD
    A[タイトル画面起動] --> B[JStickIni winmm.dll初期化]
    B --> C[JStickSearchID 接続デバイス検出]
    C --> D[*joyinit joystat配列初期化]
    D --> E[メインループ開始]
    E --> F[*getjoystat呼び出し]
    F --> G[joystatp=joystat 前フレーム保存]
    G --> H[joystat再初期化]
    H --> I{length_joylist>0?}
    I -->|Yes| J[joyGetPosEx各デバイス]
    I -->|No| K[ボタン状態なし]
    J --> L[joydata.8ビットフィールド展開]
    L --> M[joystat配列更新]
    M --> N[processKeyInput]
    N --> O[joykey->keystat変換]
    O --> P[keystator OR結合]
    P --> E
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-59-1 | デバイス検出タイミング | JStickSearchIDはタイトル画面とオプション画面の起動時に実行 | start_title, start_option |
| BR-59-2 | ボタン番号エンコード | joykey値 = デバイスID * 32 + ボタン番号（0-31） | joykey>=0 |
| BR-59-3 | 未割当キー | joykey=-1の場合はそのキーのジョイスティック入力は無効 | joykey.*.cnt=-1 |
| BR-59-4 | 範囲チェック | joykey/32 < length(joystat)の場合のみ入力を読み取る | processKeyInput, getjoykeystat |
| BR-59-5 | FXコンボ連動 | keystat.*.10=1（FXコンボボタン押下）でkeystat.6.4/5=1 | processKeyInput 266-269行目 |
| BR-59-6 | 32ボタン対応 | joyGetPosExのjoydata.8ビットフィールドから最大32ボタンを取得 | *getjoystat |
| BR-59-7 | Start/Backキー特殊処理 | getjoykeystatでkey配列のStart(11)/Back(12)/F11(13)はコメントアウト、key2のみ有効 | getjoykeystat |

### 計算ロジック

- **デバイスID抽出**: `joykey.cnt2.cnt / 32`
- **ボタン番号抽出**: `joykey.cnt2.cnt \ 32`（HSPの余り演算子）
- **ボタン押下判定**: `(joydata.8) & (pow(2, cnt)) > 0` でビットフィールドから各ボタン状態を取得

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

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

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| MMSYSERR_NODRIVER | ドライバなし | ジョイスティックドライバが存在しない | length_joylist=0、入力なしで続行 |
| JOYERR_UNPLUGGED | 未接続 | ジョイスティックが接続されていない | JStickSearchIDで検出されない |
| MMSYSERR_BADDEVICEID | 無効なID | 指定されたジョイスティックIDが無効 | joyGetPosExがエラー返却 |

### リトライ仕様

不要。毎フレームポーリング。デバイスの再検出はタイトル画面/オプション画面遷移時に実施。

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

不要。

## パフォーマンス要件

- 毎フレーム（約7ms間隔）でjoyGetPosExを全接続デバイスに対してポーリング
- joydata構造体（52バイト）の読み取りと32ボタン分のビットフィールド展開

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

特になし。

## 備考

- joykey配列のインデックス: key配列と同一構造（0-3=BT, 4-5=FX, 6-9=レーザー, 10=FXコンボ, 11=Start, 12=Back, 13=F11）
- jstick.asモジュール: Win32 Multimedia API（winmm.dll）のHSPラッパー
- オプション画面でのジョイスティックボタン割り当て: ボタン押下を検出してjoykey配列に設定
- getjoykeystatのStart/Backキーはkey配列側がコメントアウトされている（key2側のみ有効）

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | jstick.as | `third_party/jstick.as` | Win32ジョイスティックAPI定義とJSTICKモジュール |
| 1-2 | kshootmania.hsp | `kshootmania.hsp` | joykey配列の定義とconfig.iniからの読み込み |

**読解のコツ**: jstick.asは Win32 Multimedia API（joyGetPosEx, joyGetNumDevs, joyGetDevCapsA）のHSPラッパーである。joyGetPosExのjoydata構造体は13要素で、joydata.8がボタンビットフィールド（最大32ボタン）。

- **jstick.as 14-18行目**: winmm.dll関数の宣言（_joyGetPosEx, joyGetNumDevs, _joyGetDevCaps）
- **jstick.as 28-32行目**: JStickIni - ドライバ初期化とデバイス数取得
- **jstick.as 77-89行目**: JStickSearchID - 接続中デバイスIDの検出
- **kshootmania.hsp 713-737行目**: joykey[2][14]配列の初期化（config.iniのjoy=/joy2=行から読み込み）

#### Step 2: ジョイスティック状態取得

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | play_utils.hsp | `src/scene/play/play_utils.hsp` | *joyinit / *getjoystatサブルーチン |

- **135-142行目**: *joyinit - joystat/joystatp配列の初期化（length_joylist>0の場合、最大デバイスID+1 x 32）
- **144-177行目**: *getjoystat - 毎フレームのボタン状態ポーリング
  - **146行目**: joystatpに前フレーム状態を保存
  - **166行目**: joystatを再初期化
  - **169行目**: joyGetPosExでジョイスティック状態取得
  - **171-173行目**: joydata.8のビットフィールドからjoystat配列に展開（pow(2,cnt)でマスク）

#### Step 3: processKeyInput内のジョイスティック変換

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | play_key_input.hsp | `src/scene/play/play_key_input.hsp` | processKeyInput関数のジョイスティック部分 |

- **259-270行目**: ジョイスティック入力からkeystatへの変換
  - **262行目**: joykey/32でデバイスID、joykey\32でボタン番号を取得し、joystat値をkeystatにマッピング
  - **266-269行目**: FXコンボキー（keystat.*.10=1）検出時にkeystat.6.4/5=1

#### Step 4: 統合関数とオプション設定

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | kshootmania.hsp | `kshootmania.hsp` | getjoykeystat関数 |
| 4-2 | option.hsp | `src/scene/option.hsp` | ジョイスティックボタン割り当てUI |

- **kshootmania.hsp 2219-2247行目**: getjoykeystat - キーボード+ジョイスティック入力統合
  - **2241-2246行目**: joykey 2セット分のジョイスティックボタン状態をOR結合
- **option.hsp 784-807行目**: オプション画面でのジョイスティックボタン割り当て
  - **787行目**: joystat.cnt2.cnt=1 かつ joystatp.cnt2.cnt=0（ボタン押下エッジ）で検出
  - **789行目**: デバイスID*32+ボタン番号をjoykey配列に設定

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

```
start_title / start_option
    |
    +-- JStickSearchID 0,joylist (jstick.as) ... 接続デバイス検出
    |       +-- joyGetPosEx (winmm.dll) x 各デバイスID
    |
    +-- *joyinit (play_utils.hsp) ... joystat配列初期化
    |
    +-- メインループ
            |
            +-- *getjoystat (play_utils.hsp) ... 毎フレームポーリング
            |       +-- joyGetPosEx (winmm.dll) x length_joylist
            |       +-- joydata.8ビットフィールド -> joystat展開
            |
            +-- processKeyInput (play_key_input.hsp)
            |       +-- joykey/joystat -> keystat[2-5]変換
            |       +-- FXコンボ連動
            |
            +-- getjoykeystat (kshootmania.hsp)
                    +-- key/key2 getkey + joykey/joystat OR結合
```

### データフロー図

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

ジョイスティック物理デバイス --> joyGetPosEx (winmm.dll) --------> joydata.8 (ビットフィールド)
                                    |
joydata.8 (32bit) ------------> pow(2,cnt)マスク展開 -----------> joystat[N][32]
                                    |
config.ini (joy=/joy2=) ------> joykey[2][14]配列 --------------> ボタン番号マッピング
                                    |
joystat + joykey -------------> processKeyInput -----------------> keystat[2-5][0-10]
                                    |
joystat + joykey -------------> getjoykeystat ------------------> 統合キー状態
                                    |
keystat全ソース --------------> OR結合 --------------------------> keystator[0-10]
                                    |
                                    +-- ノート判定 --------------> スコア計算
                                    +-- 画面遷移 ----------------> シーン変更
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| jstick.as | `third_party/jstick.as` | ソース | Win32 Multimedia APIジョイスティックラッパー |
| play_utils.hsp | `src/scene/play/play_utils.hsp` | ソース | *joyinit / *getjoystatサブルーチン |
| play_key_input.hsp | `src/scene/play/play_key_input.hsp` | ソース | processKeyInput内のジョイスティック変換処理 |
| kshootmania.hsp | `kshootmania.hsp` | ソース | joykey配列定義、getjoykeystat関数 |
| option.hsp | `src/scene/option.hsp` | ソース | ジョイスティックボタン割り当て設定UI |
| title.hsp | `src/scene/title.hsp` | ソース | JStickSearchIDによるデバイス検出 |
| config.ini | `config.ini` | 設定 | ジョイスティックキーバインド設定（joy=/joy2=） |
