# 機能設計書 10-ソート切替

## 概要

K-Shoot MANIAの選曲画面において、通常順（ディレクトリ構造準拠）とレベル順（難易度値でソート）のソート方式をFXキー同時押しで切り替える機能の設計書である。

### 本機能の処理概要

ソート切替機能は、選曲画面（start_select）のメインループ内で動作し、FXキー2つの同時押し（fxkey.0+fxkey.1=2）によりsorttype変数を0（通常順）と1（レベル順）の間でトグルする。切替時には曲リスト全体の再生成（*refreshfilelist）が行われ、切替前の選択曲位置を保持して切替後のリスト内で同一曲の位置に自動ジャンプする。

**業務上の目的・背景**: 大量の楽曲コレクションから目的の曲を探す際、フォルダ構造ベースの通常順と、難易度レベル別に整理されたレベル順の2つの表示方式を提供することで、利用者の探索効率を向上させる。

**機能の利用シーン**: 選曲画面で任意のタイミングにFXキー2つを同時押しすることで、ソート方式が即座に切り替わる。レベル順ではLv1～Lv20のレベルフォルダが自動生成され、各楽曲が難易度別に配置される。

**主要な処理内容**:
1. FXキー同時押し検出（fxkey.0+fxkey.1=2かつ前フレームで同時押しでない）
2. sorttype変数のトグル（sorttype=1-sorttype）
3. 選択中の曲パスと難易度を保存（selpdir/selpdif）
4. 曲リスト再生成（*refreshfilelist呼び出し）
5. 切替後リストでの選択曲位置復元
6. ジャケット/スコア/楽曲情報のリフレッシュ
7. ソート切替アニメーションフラグ設定（sortfl=1,1）

**関連システム・外部連携**: ファイルシステム（楽曲ディレクトリスキャン）、config.ini（mselview設定の永続化）

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

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 3 | 曲選択画面 | 主画面 | FXキー同時押し(fxkey.0+fxkey.1=2)による通常順/レベル順のソート方式切替 |

## 機能種別

ナビゲーション・データ操作

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| sorttype | int | Yes | 現在のソート方式 | 0（通常順）/ 1（レベル順） |
| fxkey | int[2] | Yes | FXキー押下状態 | 0 or 1 |
| fxkeyp | int[2] | Yes | 前フレームのFXキー押下状態 | 0 or 1 |
| selnow | int | Yes | 現在の選択曲位置 | 0 - mnum-1 |
| levm | int | Yes | 表示用難易度 | 0-3 |

### 入力データソース

- FXキー入力（キーボード/ジョイスティック）
- config.ini の mselview 設定値（起動時読み込み）
- songs/ディレクトリ配下の.kshファイル群（リスト再生成時）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| sorttype | int | 変更後のソート方式（0 or 1） |
| minfo | int[SONG_MAX][4][n] | 再生成された曲リストの整数情報配列 |
| minfostr | str[SONG_MAX][4][n] | 再生成された曲リストの文字列情報配列 |
| mnum | int | 再生成後のリスト内アイテム総数 |
| levcnt | int[20] | レベル順ソート時の各レベル開始位置（-1=該当レベル不在） |
| sortfl | int[2] | ソート切替アニメーションフラグ |

### 出力先

- グローバル変数（sorttype, minfo, minfostr, mnum, levcnt, sortfl）
- config.ini保存時（mselview=directory or mselview=level）

## 処理フロー

### 処理シーケンス

```
1. ソート切替トリガー検出（244行目）
   ├─ fxkey.0+fxkey.1=2（同時押し）
   ├─ fxkeyp.0+fxkeyp.1<=1（前フレームは非同時押し）
   ├─ dialognow=0（ダイアログ非表示）
   └─ ginfo2()=0（ウィンドウフォーカスあり）
2. 切替処理（245-261行目）
   ├─ sorttype=1-sorttype（トグル）
   ├─ selpdir=minfostr.selnow.levm.0（現在曲パス保存）
   ├─ selpdif=levm（現在難易度保存）
   ├─ selnow=0（初期化）
   ├─ levnow=levm（難易度同期）
   ├─ gosub *refreshfilelist（リスト再生成）
   ├─ 曲位置復元ループ（251-256行目）
   │   └─ minfostr.cnt.selpdif.0=selpdirを検索し一致したらselnow=cnt
   ├─ gosub *refreshjacket
   ├─ gosub *reloadscore
   ├─ gosub *refreshscore
   ├─ gosub *refreshsonginfo
   └─ sortfl=1,1（アニメーションフラグ設定）
3. リスト再生成（*refreshfilelist内部）
   ├─ sorttype=0の場合: ディレクトリ構造ベースのスキャン（3031行目以降）
   │   └─ キャッシュファイル活用（_abc.cache/_abc.cacheinfo）
   └─ sorttype=1の場合: レベル順スキャン（3326行目以降）
       ├─ dim levcnt,20 - レベルフォルダ位置配列初期化（3329行目）
       ├─ 各.kshファイルのlevel=とdifficulty=を解析（3370-3391行目）
       ├─ レベル値に基づく挿入位置算出（3394-3409行目）
       ├─ 空レベルフォルダの削除（3411-3419行目）
       └─ キャッシュファイル保存（_lv.cache/_lv.cacheinfo）
4. config.ini保存
   └─ mselview=directory (sorttype=0) / mselview=level (sorttype=1)（1459-1463行目）
```

### フローチャート

```mermaid
flowchart TD
    A[FXキー同時押し検出] --> B{fxkey.0+fxkey.1=2<br>かつ前フレーム非同時?}
    B -->|No| Z[処理なし]
    B -->|Yes| C[sorttype=1-sorttype]
    C --> D[現在選択曲パス保存]
    D --> E[*refreshfilelist呼び出し]
    E --> F{sorttype}
    F -->|0| G[ディレクトリ構造ベースでリスト生成]
    F -->|1| H[レベル順でリスト生成]
    G --> I[位置復元ループ]
    H --> I
    I --> J[リフレッシュ処理]
    J --> K[sortfl=1,1 アニメーション開始]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | ソート方式 | sorttype=0: 通常順（ディレクトリ構造準拠）、sorttype=1: レベル順（Lv1～Lv20） | 常時 |
| BR-02 | トグル方式 | sorttype=1-sorttype で0と1を交互に切替 | FXキー同時押し時 |
| BR-03 | 位置復元 | 切替前の選択曲パス（selpdir）と難易度（selpdif）を保持し、切替後リストで同一曲に自動ジャンプ | ソート切替時 |
| BR-04 | config.ini永続化 | mselview=directory（sorttype=0）またはmselview=level（sorttype=1）として保存 | 設定保存時 |
| BR-05 | 初期値 | sorttype=0（通常順）がデフォルト | kshootmania.hsp 315行目 |
| BR-06 | レベルフォルダ | レベル順ソート時、Lv1～Lv20のレベルフォルダが自動生成され、該当楽曲が存在しないレベルは削除 | sorttype=1時 |
| BR-07 | levcnt配列 | levcnt[0..19]に各レベル開始位置を格納、楽曲なしは-1 | sorttype=1時 |
| BR-08 | FXキーフォルダジャンプ | ソート方式に応じたFXキー離し時のフォルダジャンプ動作（sorttype=0: ディレクトリ前後移動、sorttype=1: levcnt配列によるレベル前後移動） | FXキー単独離し時 |

### 計算ロジック

- sorttype切替: `sorttype=1-sorttype` （select.hsp 245行目）
- レベル順の挿入位置: `mcnt=max(levcnt.csonglevel,0)` で挿入位置を算出し、以降のlevcnt値をインクリメント（select.hsp 3398-3401行目）
- 空レベル削除: `levnum.cnt=0` のレベルフォルダをardelで削除（select.hsp 3413-3419行目）
- レベル前方ジャンプ（sorttype=1時のFXキー左離し）: levcnt配列を逆順走査し、selnowより小さい値の位置へジャンプ（select.hsp 444-450行目）
- レベル後方ジャンプ（sorttype=1時のFXキー右離し）: levcnt配列を順方向走査し、selnowより大きい値の位置へジャンプ（select.hsp 454-461行目）

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

本機能ではデータベース操作は行わない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 位置復元失敗 | 切替後リストに選択曲が見つからない | selnow=0（先頭）のまま |

### リトライ仕様

リトライ処理は実装されていない。

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

トランザクション処理は適用されない。

## パフォーマンス要件

- リスト再生成はキャッシュファイル（_abc.cache / _lv.cache）を活用し、ディレクトリ未更新時はキャッシュからロード
- SONG_MAX=8192が楽曲数上限
- レベル順ソートでは全楽曲ファイルのlevel=とdifficulty=パラメータを読み取る必要がある

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

特になし。

## 備考

- sorttype変数はconfig.iniのmselview設定として永続化される（mselview=directory or mselview=level）
- レベル順ソート時、スコア表示の難易度はsorttype=1の場合にlevstat変数で別途決定される（select.hsp 4972-4982行目）
- FXキー同時押しによるソート切替と、FXキー単独離しによるフォルダジャンプは排他的に動作する（sortfl変数で制御）

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | kshootmania.hsp | `kshootmania.hsp` | sorttype初期値・config.ini読み込み |

**読解のコツ**: sorttypeは0（通常順）と1（レベル順）の2値。config.iniのmselview設定と対応する。

- **315行目**: `sorttype=0` - デフォルト値設定
- **612-616行目**: config.ini読み込み `if(strmid(confstr.cnt,9,16)="level"):sorttype=1`
- **1459-1463行目**: config.ini保存 `mselview=directory` or `mselview=level`

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | select.hsp | `src/scene/select.hsp` | FXキー同時押しによるトグル処理 |

**主要処理フロー**:
- **215-225行目**: FXキー入力の取得（キーボード/ジョイスティック）
- **244行目**: `if((dialognow=0)&(fxkey.0+fxkey.1=2)&(fxkeyp.0+fxkeyp.1<=1)&(ginfo2()=0))` - 同時押しトリガー検出
- **245行目**: `sorttype=1-sorttype` - ソート方式トグル
- **246-247行目**: 選択曲パスと難易度の保存 `selpdir=minfostr.selnow.levm.0`、`selpdif=levm`
- **248-249行目**: selnow=0、levnow=levm で初期化
- **250行目**: `gosub *refreshfilelist` - リスト再生成
- **251-256行目**: 位置復元ループ（minfostr.cnt.selpdif.0=selpdirを検索）
- **257-260行目**: ジャケット/スコア/楽曲情報のリフレッシュ
- **261行目**: `sortfl=1,1` - アニメーションフラグ設定

#### Step 3: リスト再生成のソート分岐を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | select.hsp | `src/scene/select.hsp` | *refreshfilelist内のソート分岐 |

**主要処理フロー**:
- **3031行目**: `if(sorttype=0)` - 通常順分岐開始
  - ディレクトリ構造に基づくスキャンとキャッシュ活用
- **3326行目以降**: レベル順分岐（暗黙のelse）
  - **3329行目**: `dim levcnt,20` - 20レベル分の開始位置配列初期化
  - **3332-3335行目**: 各レベルのヘッダアイテム生成（`*Lv1`～`*Lv20`）
  - **3370-3391行目**: 各.kshファイルからlevel=とdifficulty=を解析
  - **3394-3409行目**: レベル値に基づく挿入位置算出とリスト挿入
  - **3411-3419行目**: 楽曲数0のレベルフォルダをardelで削除し、levcnt=-1に設定

#### Step 4: FXキー離し時のフォルダジャンプ（ソート方式別）

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | select.hsp | `src/scene/select.hsp` | sorttype別のジャンプ処理（320-470行目） |

**主要処理フロー**:
- **326-327行目**: FXキー離し検出（fxkey.cnt=0 & fxkeyp.cnt=1 & sortfl.cnt=0）
- **331行目**: `if(sorttype=0)` - 通常順時のディレクトリ前後移動
- **435行目**: `if(sorttype=1)` - レベル順時のlevcnt配列によるレベル間ジャンプ
  - **444-450行目**: 前方ジャンプ（levcnt逆順走査、selnowより小さい位置へ）
  - **454-461行目**: 後方ジャンプ（levcnt順方向走査、selnowより大きい位置へ）
- **469行目**: `sortfl.cnt=0` - ソートフラグリセット

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

```
start_select (src/scene/select.hsp)
    │
    ├─ FXキー入力取得 (215-225行目)
    │
    ├─ FXキー同時押し検出 (244行目)
    │      ├─ sorttype=1-sorttype (245行目)
    │      ├─ 選択曲パス保存 (246-247行目)
    │      ├─ *refreshfilelist (250行目)
    │      │      ├─ sorttype=0: ディレクトリ構造スキャン (3031行目)
    │      │      │      └─ キャッシュ活用 (_abc.cache)
    │      │      └─ sorttype=1: レベル順スキャン (3326行目)
    │      │             ├─ levcnt配列初期化 (3329行目)
    │      │             ├─ level=/difficulty=解析 (3370-3391行目)
    │      │             ├─ 挿入ソート (3394-3409行目)
    │      │             └─ 空レベル削除 (3411-3419行目)
    │      ├─ 位置復元ループ (251-256行目)
    │      ├─ *refreshjacket (257行目)
    │      ├─ *reloadscore (258行目)
    │      ├─ *refreshscore (259行目)
    │      └─ *refreshsonginfo (260行目)
    │
    └─ FXキー離し時のジャンプ (326-469行目)
           ├─ sorttype=0: ディレクトリ前後移動 (331-434行目)
           └─ sorttype=1: レベル前後移動 (435-468行目)
```

### データフロー図

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

FXキー同時押し ─────────────▶ sorttype=1-sorttype ──────────▶ sorttype変更
                                    │
selpdir/selpdif ───────────▶ 選択曲保存 ─────────────────────▶ 復元用データ
                                    │
songs/**/*.ksh ────────────▶ *refreshfilelist ────────────────▶ minfo/minfostr配列
ディレクトリ構造 ──────────▶     │                               mnum（総数）
                                    ├─ sorttype=0: ABC順 ──▶ ディレクトリ順リスト
                                    └─ sorttype=1: LV順 ───▶ レベル順リスト
                                                                    levcnt[20]
                                    │
位置復元ループ ────────────▶ selpdir検索 ────────────────────▶ selnow復元
                                    │
config.ini ────────────────▶ mselview保存 ──────────────────▶ 永続化
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| select.hsp | `src/scene/select.hsp` | ソース | ソート切替トリガー検出・リスト再生成・位置復元・フォルダジャンプ |
| kshootmania.hsp | `kshootmania.hsp` | ソース | sorttype初期値・config.ini読み込み/保存処理 |
| _abc.cache | `cache/songs/` | キャッシュ | 通常順ソートのキャッシュファイル |
| _abc.cacheinfo | `cache/songs/` | キャッシュ | 通常順ソートのキャッシュ情報ファイル |
| _lv.cache | `cache/songs/` | キャッシュ | レベル順ソートのキャッシュファイル |
| _lv.cacheinfo | `cache/songs/` | キャッシュ | レベル順ソートのキャッシュ情報ファイル |
