# ローカル開発ガイド（3 リポ · embed 更新 · G4 計測）

PoC を **8780 / 8782 / 8790 / 8765** で動かすときの、**版管理・iframe 再読込・有効プレイ再テスト**を 1 ページにまとめたものです。

正本の兄弟連携仕様: [LEGACY-CLIENTS.md](LEGACY-CLIENTS.md) · transport: [architecture/transport-sync-v2.md](architecture/transport-sync-v2.md) · G4 指標: [architecture/home-layout.md](architecture/home-layout.md)

---

## 1. 3 リポジトリ（ローカル git）

Platform と兄弟アプリは **フォルダも git も分離**します（マージしない方針 — LEGACY-CLIENTS「統合方針」）。

| リポ | ローカルパス（`アプリ/` 配下） | ポート | git ルート |
|------|--------------------------------|--------|------------|
| **Muvic Platform** | `muvic-joy-platform/` | 8780（静的）· 8782（API） | 本リポ |
| **Karaoke Web Studio** | `karaoke-app-v1.0.0/` | 8790 | 同フォルダ |
| **CDS3D** | `CDS3D/app/` | 8765（`cds3d_serve.py`） | **`app/`** 推奨 |

- **GitHub（Private · 正本）:**  
  - Platform https://github.com/jo-oku/muvic-joy-platform  
  - Karaoke https://github.com/jo-oku/muvic-karaoke-app  
  - CDS3D https://github.com/jo-oku/muvic-cds3d-app  
- **Cloudflare Pages（静的）:** https://muvic-joy-platform.pages.dev/ — [cloudflare-pages.md](cloudflare-pages.md)  
- **OneDrive 上の `.git`** は同期競合があり得る。日常は GitHub クローン推奨。
- **Platform 連携を変えたとき**は、触ったリポすべてコミット。Platform の `apps/web/config/karaoke-poc-assets.json` の **`embedVersion`** を上げると、Player 再読込時に iframe URL が更新されやすい（下記 §2）。

### 連携のキー（Platform 側）

| 項目 | 場所 |
|------|------|
| `embedVersion` | `apps/web/config/karaoke-poc-assets.json` |
| iframe 起動 URL | `js/karaoke-bridge.js` · `js/cds3d-bridge.js` |
| キャッシュバスタ `v=` | `js/embed-cache.js`（`embedVersion` + Player 起動ごとの短い ID） |
| postMessage origin | `js/embed-post-message.js`（iframe **`onload` 後**のみ · `src` origin と一致 · embedVersion **7**） |

兄弟側の Muvic 連携コード（platform git **外** · 各リポで管理）:

| アプリ | 主なファイル |
|--------|----------------|
| Karaoke | `app.js` — `muvic:transport` / `transportRequest` / `clockSync` / overlay |
| CDS3D | `js/muvic_embed.js` · `js/animation.js`（`syncPositionFromParent`） |

---

## 2. embed の再読込手順

### 2.1 いつ必要か

- Karaoke / CDS3D / Platform の **JS を変更した後**
- **`embedVersion` を上げた後**
- iframe が古い挙動のまま（transport 不整合 · Console の postMessage 大量エラー 等）

### 2.2 正しい embed URL（3D 例）

`#cds3dFrame` の `src` に **必ず**含まれること:

- `muvicEmbed=1` · `muvicMode=player` · `muvicWork=<workId>`
- 末尾 **`v=7.xxxxx`**（先頭数字 = `embedVersion`、後ろ = Player/Studio 起動ごとに変わる）

`work=` だけ・`level=high` だけの URL は **Platform embed 用ではない**（別起動の残骸や古い `src` の可能性）。

### 2.3 手順（通常はこれで足りる）

1. **8780** で `player.html?work=...&api=1` を **Ctrl+Shift+R**（スーパーリロード）
2. **3Dダンス**（または **楽譜**）タブを開く — iframe が lazy load される
3. DevTools → Elements → `#cds3dFrame` / `#karaokeFrame` / `#cds3dStudioFrame` の **`src` と `v=7.`** を確認（Studio 親バーは `#playerTransportBar` · Player と同 ID）
4. 必要なら **8765 / 8790** を iframe コンテキストに切り替えて **Ctrl+Shift+R**（兄弟 JS のキャッシュ）

**8765 を別タブで開いてハードリロードする必要は通常ない**（Player の iframe 内で読み込まれる）。

### 2.4 ホスト名

PoC では **`127.0.0.1` に統一**（8780 / 8782 / 8790 / 8765）。`localhost` と混在すると postMessage が失敗することがある。

### 2.5 古い iframe URL が残るとき

`player-shell.js` は **`danceFrame.src` が空のときだけ** URL を設定する。一度古い URL が入ると Platform だけ F5 しても残ることがある。

**対処:**

- Player **スーパーリロード** → 3D タブを開き直す
- または `embedVersion` を +1 して Player を再読込（新しい `v=`）
- 上級: Elements で `#cds3dFrame` の `src` を空にしてから 3D タブを再クリック

### 2.6 Platform 変更後のチェックリスト（Transport / 3D）

1. 四サービス起動 · `?api=1`
2. 親 transport ▶ · 3D / 楽譜が同じ秒付近で追従
3. 3D embed ▶/⏸ · 親バーと連動
4. 3D ↔ 楽譜タブ切替 · 裏 BGM が鳴り続けない
5. 再生中 Console · `postMessage` origin mismatch が雪だるma式に増えない

詳細: [transport-sync-v2.md](architecture/transport-sync-v2.md)「確認」

---

## 3. G4 有効プレイ（`duplicate_qualified_play`）

### 3.1 仕様（G1）

**同一 `sessionId` × 同一 `workId` で `work.qualified_play` は最大 1 回。**

| 経路 | 条件 |
|------|------|
| **長尺** | 再生開始 + embed 滞在 **≥ 30 秒**（`muvic:embedEngaged` 累計） |
| **短尺** | 再生開始 + **自然終了**（`muvic:playbackCompleted`）かつ **5 秒 ≤ playedMs < 30 秒** |

8780 `player-engagement-events.js` → `POST /events` · 8782 `events-ingest.mjs` で検証・重複拒否。

**G4 開始イベント:** 親 transport ▶ でも `player-transport.js` → `notifyPlaybackStarted`（Karaoke/3D の postMessage に加えて）。**尺終端**は親 `durationSec` で pause 時に `playbackCompleted` 相当を送る。Karaoke embed は **`muvic:session.durationSec`** で曲終了を cap（譜面 XML が長くても Work 尺で G4 短尺可）。

### 3.2 Network に `duplicate_qualified_play`（409）が出るとき

**多くは再テスト時の想定動作**（1 回目は成功済み）。

- `sessionId` は `localStorage` の **`muvic_play_session_id`**（タブを閉じても同じブラウザでは継続）
- API 起動時に **`apps/web/data/play-events.jsonl`** の過去ログも重複判定に読み込まれる

**確認:**

- Network で **1 本目**の `POST .../events` が 200 か
- jsonl に同じ `workId` + `sessionId` の行が既にないか

### 3.3 もう一度「初回カウント」を試す（dev）

| 方法 | 操作 |
|------|------|
| **新 session** | Console: `localStorage.removeItem('muvic_play_session_id')` → Player F5 |
| **シークレット** | 新しい `sessionId` で 1 回目から試せる |
| **別 Work** | 新規投稿 ID なら同 session でも 1 回目可 |
| **データリセット** | `play-events.jsonl` から該当行削除 → `npm run dev:api` 再起動 |

**Player UI（PoC）:** 2 回目以降の `POST /events` が **409** のとき、画面下部にトースト（`js/muvic-toast.js` · `player-engagement-events.js`）で「既にカウント済み」と表示。初回 200 はサイレント。

再テストは引き続き上記（session リセット / シークレット / 別 Work / jsonl 削除）で分ける。

---

## 4. 投稿 Work のマスター BGM（8782）

`submit.html` で **任意**の `.mp3` / `.wav`（最大 10MB）を添付すると、8782 が `apps/web/data/uploads/{workId}/master.mp3`（または `master.wav`）に保存し、API enrich で **`assets.masterAudio.url`** を付与します。Player は **「マスター BGM（親）」** + `muvic:session` の `masterBgmActive: true` になります（`?api=1` 必須）。

| 確認 | 操作 |
|------|------|
| **API** | 投稿後 `GET http://127.0.0.1:8782/works/<workId>` → `assets.masterAudio.url` が `http://127.0.0.1:8782/assets/<workId>/master.wav` 等 |
| **再生ファイル** | 上記 URL をブラウザで開き **200**（404 なら 8782 再起動前の投稿 · ID 誤りを疑う） |
| **Player** | `player.html?work=<workId>&api=1` → transport 右 **「マスター BGM（親）」** · 親 ▶ でアップロード音 |
| **尺** | 振付あり → motion JSON の `duration`。**譜面のみ + BGM** → WAV/MP3 から `durationSec` 自動（失敗時は手入力） |
| **後付け BGM** | 作成者 · `work.html?work=<id>&api=1` → **マスター BGM（後付け）** · `PATCH /works/:id` + `masterAudioBase64` |
| **未添付** | `masterAudio` なし → **サイレントクロック**（デモ ID のみ `karaoke-poc-assets.json` の `bgmFallback`） |

**8782 をコード変更後に再起動**してから投稿・再確認してください。

### 4.1 説明文 · カバー（D3 · 8782）

| 項目 | 操作 |
|------|------|
| **投稿** | `submit.html?api=1` — 説明文（任意）· カバー `.png/.jpg/.webp`（最大 2MB） |
| **API** | `POST /works` / `PATCH /works/:id` — `description` · `coverImageBase64` + `coverImageFileName` |
| **保存先** | カバー → `apps/web/assets/covers/{workId}.{ext}` · enrich で `coverUrl`（8780 静的配信） |
| **詳細 UI** | 作成者 · `work.html?work=<id>&api=1` — **説明文 · カバー画像** パネルで PATCH |
| **表示** | `GET /works/:id` の `description` · ホーム / Explore **グリッド**（`works-ui` · 2 行）· ヒーロー · Work 詳細 |

### 4.2 自動 `muvic:session`（Console · 任意）

1. Player `127.0.0.1:8780` · 楽譜タブ表示後、DevTools Console を **`karaokeFrame`（8790）** に切り替え、リスナーを貼る（[transport-sync-v2.md](architecture/transport-sync-v2.md) embed 節）。
2. 楽譜 load 直後に **`[8790 ← parent] muvic:session`**（`masterBgmActive: true` 等）が出れば OK。embed v7 以降は未ロード向け postMessage を送らないため、origin mismatch の雪だるmaが減る想定。
3. **`127.0.0.1` に統一**（`localhost` 混在は Karaoke → 親の postMessage 失敗の原因）。

---

## 5. 起動コマンド（再掲）

```powershell
# 8780 — platform リポ根
py -m http.server 8780 --bind 127.0.0.1

# 8782 — platform リポ根
npm run dev:api

# 8790
cd ..\karaoke-app-v1.0.0
py -m http.server 8790 --bind 127.0.0.1

# 8765
cd ..\CDS3D\app
py cds3d_serve.py
```

Player 例: http://127.0.0.1:8780/apps/web/player.html?work=work_demo_001&api=1

Studio 例: http://127.0.0.1:8780/apps/web/studio.html?work=work_demo_001&api=1 — 親 transport + CDS3D フル UI（[transport-sync-v2.md](architecture/transport-sync-v2.md) Studio 節）

---

## 6. 関連 README 節

Platform [README.md](../README.md) — 投稿フロー · P1b 重ね録り · G3 概要 · よくある症状（簡易表）

**回帰チェックリスト（☑ 形式 · ミニ/標準/フル）:** [regression-checklist.md](regression-checklist.md)
