# 機能設計書 81-コース結果表示

## 概要

本ドキュメントは、K-ShootManiaにおけるコース結果表示機能の設計を記述する。コースモードで複数楽曲を連続プレイした後のリザルト画面で、各曲のスコア一覧、合計スコア、ゲージ状態、グレード、クリアメダルなどを表示し、スコアをファイルに保存する機能である。

### 本機能の処理概要

**業務上の目的・背景**：コースモードは複数楽曲を連続でプレイする高難度チャレンジ機能である。プレイヤーは各曲ごとの成績と総合成績を把握し、過去のベストスコアとの比較を行う必要がある。コース結果画面は、コースモード完了後にプレイヤーへ総合的なフィードバックを提供し、成績をファイルに永続化するための不可欠な機能である。

**機能の利用シーン**：コースモードで全曲のプレイが完了した後、自動的にこの画面に遷移する。プレイヤーは各曲のスコア、難易度、グレード、クリアメダルをスクロール可能なリストで確認し、合計スコアとゲージパーセンテージを確認する。ハイスコア更新時はNEW RECORDアニメーションが表示される。また、Twitter連携によるスコア共有機能も提供される。

**主要な処理内容**：
1. 各曲の譜面ファイルからタイトル・アーティスト・難易度・レベル・ジャケット画像を読み込み、リスト表示用画像バッファ(IMG_CRESULT_LISTBUF)を構築
2. 合計スコア(coursetotalscore)を曲数で割り平均化し、ゲージパーセンテージ(gpercent)を計算
3. クリア判定(stat6: 0=FAILED/1=CLEARED/2=FULL COMBO/3=PERFECT)をゲージ状態とエラー数から算定
4. プレイヤー別コーススコアファイル(.ksc)に成績データを保存
5. NEW RECORD判定(diff>0の場合)とアニメーション表示
6. リストのスクロール操作、ゲージバー表示、グレード画像表示
7. Twitter連携（F11キーまたはマウスクリック）によるスコア共有

**関連システム・外部連携**：BASS Audio Library(コースリザルトBGM再生)、Twitterウェブインテント(スコア共有)、ファイルシステム(スコア保存)

**権限による制御**：auto=1(オートプレイ)またはadjmode=1(調整モード)の場合、スコア保存をスキップして選曲画面に直接戻る。iscmdline=1(コマンドライン起動)の場合もスコア保存をスキップする。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 7 | コースリザルト画面 | 主画面 | コース内各曲のスコア一覧・合計スコア・ゲージ・グレードのスクロール可能リスト表示 |
| 3 | 曲選択画面 | 遷移先画面 | Enter/Escキー押下でSCENE_SELECTへ遷移 |

## 機能種別

データ表示 / 計算処理 / ファイル保存

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| courselistnum | int | Yes | コース内曲数 | 1以上の整数 |
| courselist | string | Yes | コース内楽曲パスリスト（notesel形式） | 改行区切りファイルパス |
| course_sc | int配列 | Yes | 各曲のスコア | 0-10000000 |
| course_gr | int配列 | Yes | 各曲のグレード | 0-6 |
| course_md | int配列 | Yes | 各曲のメダル | 0-7 |
| coursetotalscore | int | Yes | 合計スコア | 0-10000000 |
| gauge | int | Yes | 最終ゲージ値 | 整数値 |
| effratetype | int | Yes | ゲージタイプ(-1:EASY/0:NORMAL/1:HARD) | -1, 0, 1 |
| auto | int | Yes | オートプレイフラグ | 0 or 1 |
| adjmode | int | Yes | 調整モードフラグ | 0 or 1 |
| coursecritical | int | Yes | コース全体CRITICAL数 | 0以上 |
| coursenear | int | Yes | コース全体NEAR数 | 0以上 |
| courseerror | int | Yes | コース全体ERROR数 | 0以上 |
| courseerrorn | int | Yes | コース全体ERROR総数 | 0以上 |
| ccoursetitle | string | Yes | コースタイトル | 文字列 |
| ccoursefile | string | Yes | コースファイル名 | ファイルパス文字列 |

### 入力データソース

- プレイ画面(SCENE_PLAY)から引き継がれるグローバル変数群
- コースファイル(.kco)から読み込まれた楽曲リスト情報
- 各楽曲の譜面ファイル(.ksh)から読み込まれるメタデータ

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| 合計スコア(coursetotalscore) | int | 各曲スコア平均値（0-10,000,000） |
| ゲージパーセンテージ(gpercent) | int | 達成率（0-100+ボーナス） |
| クリア判定(stat6) | int | 0:FAILED/1:CLEARED/2:FULL COMBO/3:PERFECT |
| ハイスコア更新差分(diff) | int | 前回スコアとの差分 |
| スクロール位置(scrollposnow) | int | リスト表示のスクロールオフセット |

### 出力先

- 画面表示：コースリザルト画面全体（各曲リスト、スコア、ゲージ、グレード、NEW RECORD通知）
- ファイル保存：`courses/score/{プレイヤー名}/{コースファイル名}.ksc`

## 処理フロー

### 処理シーケンス

```
1. 初期化判定
   └─ auto=1またはadjmode=1の場合、SCENE_SELECTへ直接遷移（スコア保存スキップ）
2. 画像リソース読み込み
   └─ result.png, result_grade.png, courseresult_newrecord.png, result_top.png等のテクスチャをロード・スケーリング
3. 各曲リストバッファ構築（courselistnum回ループ）
   └─ a. 譜面ファイルパスの解決（直接パス→代替パス）
   └─ b. 譜面ファイル読み込みとUTF-8/Shift-JIS判定
   └─ c. title, artist, difficulty, level, jacket情報の抽出
   └─ d. ジャケット画像・曲名・アーティスト名・難易度・レベル・スコア・グレード・メダルをIMG_CRESULT_LISTBUFに描画
4. コース情報パネル構築
   └─ コースタイトルをIMG_CRESULT_INFOに描画
5. 合計スコア・パーセンテージ計算
   └─ coursetotalscore /= courselistnum
   └─ ゲージ状態に基づくgpercent計算
6. クリア判定(stat6)の算定
   └─ ゲージ残量、エラー数0判定、パーフェクト判定
7. スコアファイル保存処理
   └─ a. 既存スコアファイル(.ksc)の読み込み
   └─ b. オプション文字列(effratetype/turn/モード)でのスコアエントリ照合
   └─ c. ハイスコア・クリア判定・プレイ回数の更新
   └─ d. ファイル書き込み
8. NEW RECORD判定とアニメーション準備
   └─ diff>0の場合、旧スコアと新スコアの数値画像を構築
9. メインループ（描画・入力処理）
   └─ a. リスト表示のスクロール操作（上下キー）
   └─ b. ゲージバー描画
   └─ c. NEW RECORDフェードイン/アウトアニメーション
   └─ d. Twitter投稿処理（F11キー/マウスクリック）
   └─ e. Enter/Escキーで選曲画面へ遷移
```

### フローチャート

```mermaid
flowchart TD
    A[コース結果画面開始] --> B{auto=1 or adjmode=1?}
    B -->|Yes| C[SCENE_SELECTへ直接遷移]
    B -->|No| D[画像リソース読み込み]
    D --> E[各曲リストバッファ構築]
    E --> F[コース情報パネル構築]
    F --> G[合計スコア・パーセンテージ計算]
    G --> H[クリア判定算定]
    H --> I{auto=0 and adjmode=0?}
    I -->|Yes| J[スコアファイル保存]
    I -->|No| K[保存スキップ]
    J --> L[NEW RECORD判定]
    K --> L
    L --> M[メインループ開始]
    M --> N[描画処理・スクロール・UI]
    N --> O{Enter/Esc押下?}
    O -->|No| N
    O -->|Yes| P[SCENE_SELECTへ遷移]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-81-01 | 合計スコア平均化 | coursetotalscore /= courselistnum で曲数による平均化 | 常時 |
| BR-81-02 | ゲージパーセンテージ計算 | ゲージ残量0以下or中断(tochu=1)の場合は進捗率ベース、そうでなければ100+ボーナス | ゲージ状態による分岐 |
| BR-81-03 | クリア判定 | ERROR0=FULL COMBO、スコア10000000=PERFECT、ゲージ残>0=CLEARED、それ以外=FAILED | プレイ結果による判定 |
| BR-81-04 | メダル判定のゲージタイプ反映 | effratetypeがHARD(1)/EASY(-1)の場合、メダル値を補正(HARD:+16系、EASY:+8系) | effratetype値による |
| BR-81-05 | NEW RECORD判定 | 旧gpercent < 新gpercentの場合にdiff>0でNEW RECORD表示 | スコアファイル存在時 |
| BR-81-06 | オートプレイ・調整モード保存除外 | auto=1, adjmode=1, iscmdline=1の場合、スコア保存をスキップ | プレイモードによる |
| BR-81-07 | 中断時処理 | tochu=1の場合、stat6=0(FAILED)に設定 | コース中断時 |

### 計算ロジック

**合計スコア平均化**:
```
coursetotalscore = coursetotalscore / courselistnum
```

**ゲージパーセンテージ計算**:
```
if gauge/1000 <= 0 or tochu=1:
  gpercent = int((coursenow-1)/courselistnum + mendt/t_finalnote/courselistnum * (tochu=1)) * 100
else:
  gpercent = 100 + max(coursetotalscore - 9000000, 0) / 10000
```

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

本機能はデータベースを使用せず、ファイルシステムベースでスコアを管理する。

### 操作別データベース影響一覧

| 操作 | 対象ファイル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| スコア保存 | courses/score/{player}/{course}.ksc | READ/WRITE | コーススコアの読み込みと更新保存 |

### ファイル別操作詳細

#### コーススコアファイル(.ksc)

| 操作 | 項目 | 更新値・取得条件 | 備考 |
|-----|------|-----------------|------|
| READ | 既存スコアエントリ | オプション文字列(effratetype,turn,mode等)で照合 | カンマ区切りフォーマット |
| WRITE | スコアエントリ | "{options}={score},{clear},{grade},{gpercent},{chainmax},{playcount},{clearcount},{fullcombocount},{perfectcount}" | 既存エントリは上書き、新規エントリは追記 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | ファイル不存在 | 譜面ファイルが存在しない場合(strsize<0) | 該当曲のリスト表示をスキップ |
| - | スコアファイル不存在 | 初回プレイ時にスコアファイルが無い場合 | 新規作成してスコアを保存 |
| - | ディレクトリ不存在 | score/{player}ディレクトリが無い場合 | mkdirで自動作成 |

### リトライ仕様

リトライは実装されていない。ファイルI/Oエラー時は処理を続行する。

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

ファイルベースのスコア保存のため、トランザクション管理は行われない。notesel/notesaveによる一括書き込みで擬似的な原子性を確保している。

## パフォーマンス要件

- メインループはawait 7(約7ms間隔)で描画更新
- リストスクロール時のフェードアニメーション(200ms)
- NEW RECORDアニメーションのフェードイン(350ms後150ms)
- 画面遷移前に200msの黒画面待機

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

- スコアファイルは平文テキストで保存されており、改ざん防止機構はない
- Twitter投稿時のテキストはURLエンコードされてブラウザ経由で送信

## 備考

- コース結果画面はSCENE_COURSE_RESULTシーンとして管理される
- IMG_CRESULT_で始まる定数はコース結果画面専用の画像バッファIDである
- リスト表示は4曲以上の場合にスクロール機能が有効化される（courselistnum>=4）
- スクリーンショットはF11キー押下時にクリップボードにコピーされ、ブラウザでTwitter投稿画面が開かれる

---

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

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

### 推奨読解順序

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

コース結果表示に関連するグローバル変数群を理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | kshootmania.hsp | `kshootmania.hsp` | コース関連変数の初期化(iscourse, courselistnum等)を確認 |

**読解のコツ**: HSPではグローバル変数が多用される。course_で始まる変数群がコース結果画面用、IMG_CRESULT_で始まる定数が画像バッファIDである。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | course_result.hsp | `src/scene/course_result.hsp` | on_update_course_result関数が唯一のエントリーポイント |

**主要処理フロー**:
1. **1行目**: `#deffunc on_update_course_result` - コース結果画面の更新関数定義
2. **5-21行目**: auto/adjmodeチェックとスキップ処理
3. **22-68行目**: 画像リソースのロードとスケーリング
4. **71-209行目**: 各曲リストバッファ(IMG_CRESULT_LISTBUF)の構築ループ
5. **242-249行目**: 合計スコア・パーセンテージ計算
6. **250-263行目**: クリア判定(stat6)の算定
7. **265-371行目**: スコアファイル保存処理
8. **375-400行目**: NEW RECORD判定とアニメーション準備
9. **456-632行目**: メインループ（描画・入力処理・遷移制御）

#### Step 3: 各曲リストバッファ構築を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | course_result.hsp | `src/scene/course_result.hsp` | 71-209行目のrepeat courselistnumループ |

**主要処理フロー**:
- **73-83行目**: 譜面ファイルパスの解決（直接パスと代替パスの2段階探索）
- **86-91行目**: UTF-8/Shift-JIS自動判定とBOM処理
- **92-125行目**: 譜面メタデータ(title, artist, difficulty, level, jacket)の抽出
- **127-155行目**: 曲名・アーティスト名のテキスト描画（フォントサイズ自動調整）
- **169-172行目**: ジャケット画像の読み込みと描画
- **174行目**: 難易度表示
- **176-179行目**: レベル数値の描画
- **181-190行目**: スコアの8桁0埋め描画
- **192行目**: グレード画像の描画
- **194-207行目**: クリアメダルのゲージタイプ別補正と描画

#### Step 4: スコア保存処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | course_result.hsp | `src/scene/course_result.hsp` | 265-371行目のスコア保存ブロック |

**主要処理フロー**:
- **266-288行目**: オプション文字列(stat5_)の構築（effratetype, turn, モード設定）
- **292-296行目**: スコアファイルパスの構築
- **300-346行目**: 既存スコアファイルの読み込みとエントリ照合
- **312-335行目**: ハイスコア更新判定とプレイカウント・クリアカウント更新
- **347-349行目**: 新規エントリ追加（初回プレイ時）
- **350-370行目**: ディレクトリ作成とファイル書き込み

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

```
kshootmania.hsp (メインループ)
    │
    └─ on_update_course_result (course_result.hsp:1)
           │
           ├─ 画像リソースロード (alCreateImageByFile, alStretchImageToImage)
           │
           ├─ 各曲リストバッファ構築 (71-209行目)
           │      ├─ noteload (譜面ファイル読み込み)
           │      ├─ DeleteUTF8BOM (BOM除去)
           │      ├─ utf8enc (文字エンコード変換)
           │      ├─ alDrawText (テキスト描画)
           │      └─ alStretchImageToImage (画像スケーリング描画)
           │
           ├─ スコア保存処理 (265-371行目)
           │      ├─ noteload/notesave (ファイルI/O)
           │      └─ mkdir (ディレクトリ作成)
           │
           ├─ メインループ (456-632行目)
           │      ├─ *getjoystat (ジョイスティック状態取得)
           │      ├─ getjoykeystat (ジョイスティックキー判定)
           │      ├─ *volactive (音量制御)
           │      ├─ alCopyImageToScreen (画像描画)
           │      ├─ BASS_ChannelSetAttribute (BGM音量制御)
           │      ├─ BASS_ChannelPlay (BGM再生)
           │      ├─ ClipB_SetBmp (スクリーンショット)
           │      └─ urlencode (Twitter投稿URL生成)
           │
           └─ 画像リソース解放 (633-638行目)
                  └─ alDeleteImage
```

### データフロー図

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

courselist         ───> 譜面ファイル読み込み ──────> IMG_CRESULT_LISTBUF
(楽曲パスリスト)           (title/artist/jacket)            (リスト表示バッファ)

course_sc/gr/md    ───> リストバッファ構築 ─────────> 画面描画
(各曲成績)                (スコア/グレード/メダル描画)

gauge/             ───> スコア・パーセンテージ計算 ──> gpercent
coursetotalscore          (クリア判定)                     stat6

既存.kscファイル   ───> スコア照合・更新 ──────────> 更新済.kscファイル
                          (ハイスコア判定)                  diff(NEW RECORD判定)

キー入力           ───> メインループ ──────────────> SCENE_SELECT遷移
(Enter/Esc/上下)          (スクロール/遷移制御)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| course_result.hsp | `src/scene/course_result.hsp` | ソース | コース結果画面のメイン処理 |
| kshootmania.hsp | `kshootmania.hsp` | ソース | メインループ・シーン遷移管理・config保存 |
| result.png | `imgs/result.png` | リソース | リザルト画面背景画像 |
| result_grade.png | `imgs/result_grade.png` | リソース | グレード表示画像(D-S) |
| courseresult_newrecord.png | `imgs/courseresult_newrecord.png` | リソース | NEW RECORD表示画像 |
| courseresult_info.png | `imgs/courseresult_info.png` | リソース | コース情報パネル画像 |
| courseresult_item.png | `imgs/courseresult_item.png` | リソース | 曲リストアイテム画像 |
| courseresult_arrow.png | `imgs/courseresult_arrow.png` | リソース | スクロール矢印画像 |
| courseresult_gauge.png | `imgs/courseresult_gauge.png` | リソース | ゲージバー画像 |
| result_top.png | `imgs/result_top.png` | リソース | クリアステータス表示画像 |
| {course}.ksc | `courses/score/{player}/` | データ | コーススコアファイル |
