# 機能設計書 35-BPM変化・拍子変化対応

## 概要

本ドキュメントは、K-Shoot MANIAのゲームプレイにおけるBPM変化・拍子変化対応機能の設計を記述する。譜面内のBPM変更と拍子変更に対応したカウント値（tick）計算とタイミング同期を行う。

### 本機能の処理概要

**業務上の目的・背景**：楽曲のBPMや拍子が途中で変化する譜面に対応するため、現在のBPMに基づいたtick値（内部カウント値）の正確な計算が必要である。これにより、ノートの表示位置やスクロール速度が正しく制御される。

**機能の利用シーン**：BPMが変化する楽曲のプレイ時に、BPMリスト（bpml配列）を参照して現在位置のカウント値を算出する。拍子変更時は小節の区切り位置が変わるため、beatl配列を参照して小節/拍の構造を更新する。

**主要な処理内容**：
1. calculateCurrentTick関数で、BPM変化リスト（bpml配列）を走査して現在のtick値（cnow）を算出
2. BPM変化タイミング（bpml.cnt.2: 時間、bpml.cnt.1: BPM値、bpml.cnt.0: tick位置）を参照
3. 現在時刻tとBPM変化時刻の差分から、現在のtick位置を計算: `cnow += (t - tt) * bpm / 24000`
4. bpmfcnt（BPM変化位置のキャッシュカウンタ）による走査最適化
5. 拍子変更はbeatl配列で管理（beatl.cnt.0: tick位置、beatl.cnt.1: 拍子分子、beatl.cnt.2: 拍子分母）

**関連システム・外部連携**：譜面読み込み（play_chart_load.hsp）でBPMリストとbeat変更リストを構築。

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

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 5 | ゲームプレイ画面 | 主画面 | 譜面内のBPM変更と拍子変更に対応したカウント値・タイミング計算 |

## 機能種別

計算処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| t | int | Yes | 現在の再生時刻（ms） | 整数 |
| bpml | array | Yes | BPM変化リスト（cnt.0=tick, cnt.1=BPM*1000, cnt.2=時刻ms） | 要素数bpmlnum |
| bpmlnum | int | Yes | BPM変化リストの要素数 | 1以上 |
| bpmlcfcnt | int | Yes | BPM変化リスト走査のキャッシュカウンタ | 0以上 |
| beatl | array | Yes | 拍子変更リスト（cnt.0=tick, cnt.1=分子, cnt.2=分母） | 要素数beatlnum |

### 入力データソース

譜面読み込み処理（loadKSHChartFile/parseKSHChartParamLines）で構築されたBPM変化リスト（bpml配列）、拍子変更リスト（beatl配列）。

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| cnow | int | 現在のtick位置（内部カウント値） |
| bpm | int | 現在のBPM値（実値x1000） |
| beatn | int | 現在の拍子分子 |
| beatd | int | 現在の拍子分母 |

### 出力先

- メモリ: ゲームロジック全体で参照される現在位置情報

## 処理フロー

### 処理シーケンス

```
1. cnow=0, tt=0 で初期化
2. bpml配列をbpmlcfcntから走査
   ├─ bpml.cnt.2 < t の場合: 現在時刻以前のBPM変化を適用
   │   ├─ tt = bpml.cnt.2（直近のBPM変化時刻）
   │   ├─ cnow = bpml.cnt.0（直近のBPM変化tick位置）
   │   └─ bpmlcfcnt = cnt（キャッシュ更新）
   └─ bpml.cnt.2 >= t の場合: break（走査終了）
3. bpm = bpml.bpmlcfcnt.1（現在のBPM）
4. cnow += round((t - tt) * bpm / 24000)（残り時間分のtick加算）
```

### フローチャート

```mermaid
flowchart TD
    A[calculateCurrentTick] --> B[cnow=0, tt=0]
    B --> C{bpmlnum > 0?}
    C -->|No| D[return]
    C -->|Yes| E[bpmlcfcntからループ開始]
    E --> F{bpml.cnt.2 < t?}
    F -->|Yes| G[tt=bpml.cnt.2, cnow=bpml.cnt.0, bpmlcfcnt更新]
    G --> H[次のBPM変化へ]
    H --> F
    F -->|No| I[break]
    I --> J[bpm = bpml.bpmlcfcnt.1]
    J --> K["cnow += round((t-tt)*bpm/24000)"]
    K --> D
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-35-01 | BPM変化順序走査 | BPM変化リストを時系列順に走査し、現在時刻以前の最新BPMを適用 | 毎フレーム |
| BR-35-02 | tick計算式 | cnow = 基準tick + round((現在時刻 - 基準時刻) * BPM / 24000) | 毎フレーム |
| BR-35-03 | キャッシュ最適化 | bpmlcfcntにより、過去に通過したBPM変化を再走査しない | 毎フレーム |
| BR-35-04 | BPM内部単位 | BPM値は実値x1000で管理（例: 120BPM → 120000） | 常時 |

### 計算ロジック

```
// tick位置の計算
cnow = bpml[最後に通過したBPM変化].tick位置
     + round((t - bpml[最後に通過したBPM変化].時刻) * bpm / 24000)

// UNIT_MEASURE = 48（1小節のtick数基準値）
// 24000は定数: 60秒 * 1000ms / UNIT_MEASURE * (BPMの1000倍補正)

// 拍子変化
// beatl配列: beatl.cnt.0=tick位置, beatl.cnt.1=拍子分子, beatl.cnt.2=拍子分母
// 1小節 = UNIT_MEASURE * beatn / beatd ticks
```

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

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

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | BPMリスト空 | bpmlnum=0 | 初期BPM(120)を使用して計算 |

### リトライ仕様

リトライは不要。

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

トランザクション管理は不要。

## パフォーマンス要件

毎フレーム呼び出されるが、bpmlcfcntによるキャッシュ最適化により、平均的にはO(1)の計算量。

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

特になし。

## 備考

- BPMは内部的に1000倍値で管理される（例: 120 BPM = 120000）
- c2t関数（tick→時間変換）、b2c関数（小節→tick変換）も同じbpml配列を参照する
- HiSpeedがC値（定数速度）の場合、cnowではなくc2t関数経由で時間ベースのスクロール計算が行われる

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | play_game_logic.hsp | `src/scene/play/play_game_logic.hsp` | initGamePlayVariables関数（22-24行目）でbpm=120000、beatn=4、beatd=4の初期値を確認 |

**読解のコツ**: bpml配列は3次元: bpml.cnt.0=tick位置、bpml.cnt.1=BPM*1000、bpml.cnt.2=再生時刻(ms)。

#### Step 2: calculateCurrentTick関数を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | play_game_logic.hsp | `src/scene/play/play_game_logic.hsp` | calculateCurrentTick関数（112-126行目）の全体ロジック |

**主要処理フロー**:
- **113-114行目**: cnow=0, tt=0の初期化
- **116-122行目**: bpml配列の走査（bpmlcfcntからの最適化ループ）
- **123行目**: `bpm=bpml.bpmlcfcnt.1` - 現在BPMの設定
- **124行目**: `cnow+=int(round(double(t-tt)*bpm/24000))` - 残り時間のtick変換

#### Step 3: 呼び出し元と関連関数を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | play_update.hsp | `src/scene/play/play_update.hsp` | 9行目のcalculateCurrentTick呼び出し |
| 3-2 | play_utils.hsp | `src/scene/play/play_utils.hsp` | c2t関数（210-226行目）: tick→時間変換 |
| 3-3 | play_utils.hsp | `src/scene/play/play_utils.hsp` | b2c関数（179-191行目）: 小節→tick変換 |

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

```
on_update_play (play_update.hsp)
    |
    +-- calculateCurrentTick (play_game_logic.hsp:112-126)
    |       +-- bpml配列走査
    |       +-- cnow算出
    |       +-- bpm更新
    |
    +-- c2t (play_utils.hsp:210-226)  ※HiSpeed C値モード時
    |       +-- tick→時間変換（bpml参照）
    |
    +-- b2c (play_utils.hsp:179-191)  ※小節→tick変換
            +-- beatl配列参照
```

### データフロー図

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

t (現在時刻) ────────> calculateCurrentTick
bpml配列     ────────>   BPM変化リスト走査        cnow (現在tick)
bpmlcfcnt    ────────>   キャッシュ最適化         bpm  (現在BPM)
                         tick計算
                                                 → ノート判定・描画に使用
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| play_game_logic.hsp | `src/scene/play/play_game_logic.hsp` | ソース | calculateCurrentTick関数、BPM初期値設定 |
| play_utils.hsp | `src/scene/play/play_utils.hsp` | ソース | c2t、b2c等のtick/時間/小節変換関数 |
| play_update.hsp | `src/scene/play/play_update.hsp` | ソース | calculateCurrentTickの呼び出し |
| play_chart_load.hsp | `src/scene/play/play_chart_load.hsp` | ソース | bpml配列・beatl配列の構築 |
