Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
3003c1b
Add A2E + Japanese audio test suite
claude Feb 20, 2026
cdca526
Add ONNX error diagnostic and VAD handler patch tools
claude Feb 20, 2026
081f904
Add standalone A2E Japanese audio test script
claude Feb 20, 2026
35838a6
Add LLM handler patch for Gemini dict content TypeError
claude Feb 20, 2026
b50178e
Update Gemini model to gemini-2.5-flash (2.0-flash deprecated)
claude Feb 20, 2026
cbc1c7c
Add ASR language patch to force Japanese in SenseVoice
claude Feb 20, 2026
59387bf
Add config patch script to Japanize existing chat_with_lam.yaml
claude Feb 20, 2026
a58395b
Fix ASR 2nd inference 24x slowdown causing system freeze
claude Feb 20, 2026
0875af7
Add audio2exp-service microservice and frontend A2E integration
claude Feb 21, 2026
37ebe2c
Support flat model directory layout for A2E checkpoint
claude Feb 21, 2026
23f10de
Add complete A2E-integrated concierge-controller.ts replacement
claude Feb 21, 2026
2388a86
Replace concierge-controller.ts with actual gourmet-sp version
claude Feb 21, 2026
949bbae
Fix start.sh: uvicorn → gunicorn for Flask WSGI app
claude Feb 21, 2026
76bd40c
Add comprehensive system architecture documentation
claude Feb 21, 2026
cde7c54
fix: correct data format mismatch in applyExpressionFromTts for lip sync
claude Feb 21, 2026
2e09277
fix: integrate ExpressionManager for A2E lip sync (replace broken lam…
claude Feb 21, 2026
3b91d52
fix: inline ExpressionManager to eliminate external import dependency
claude Feb 21, 2026
c2a881c
fix: use LAMAvatar's buffer system instead of non-existent guavaRenderer
claude Feb 21, 2026
461ee0a
fix: add A2E chain diagnostics after analyzing actual LAMAvatar.astro…
claude Feb 21, 2026
0df04b9
fix: match original gourmet-sp behavior - remove destructive clearFra…
claude Feb 21, 2026
4332c8f
fix: prevent autoplay deadlock in all play-and-wait patterns (STT fix)
claude Feb 22, 2026
978b5d3
fix(frontend-patch): applyExpressionFromTts の両形式対応と try/catch 追加
claude Feb 22, 2026
c9cf14c
feat: rewrite A2E patch based on production code - restore GVRM integ…
claude Feb 22, 2026
8887cc2
fix: use gourmet-sp version of concierge-controller.ts (LAMAvatar, no…
claude Feb 22, 2026
65db8dc
feat: improve A2E lip sync quality - amplify mouth blendshapes + inte…
claude Feb 22, 2026
50f4e4d
fix: handle new a2e_engine response format (plain arrays vs {weights}…
claude Feb 22, 2026
c15162e
fix: use proper INFER pipeline for A2E decoder + add renderer diagnostic
claude Feb 22, 2026
8f99c70
fix: resolve INFER pipeline startup errors for A2E service
claude Feb 22, 2026
a8a68c3
chore: add .gitignore for audio2exp-service model files
claude Feb 22, 2026
e1b8d30
fix: disable Flask auto dotenv loading to avoid encoding errors
claude Feb 22, 2026
2e16f78
fix: enable TTS playback for text input in concierge mode
claude Feb 22, 2026
dc85ffd
docs: add session handoff document for next AI session
claude Feb 22, 2026
bc1fe80
test: add CI-friendly pytest suite for A2E service PoC validation
claude Feb 22, 2026
7a2ab7c
fix: add .gcloudignore to include models/ in Cloud Build context
claude Feb 22, 2026
ac3a861
docs: update .gitignore comment to reflect bake-in strategy
claude Feb 22, 2026
a9fc0b5
fix: use Cloud Run PORT env var instead of hardcoded 8081
claude Feb 22, 2026
a633fe1
fix: lazy-load A2E engine in background thread
claude Feb 22, 2026
a4eb9ee
Pre-bake wav2vec2-base-960h in Docker image to avoid runtime download
claude Feb 22, 2026
9f5bd88
Use CPU-only PyTorch + add torchaudio for INFER pipeline
claude Feb 22, 2026
e36190d
fix(audio2exp): add warmup timeout and forward-pass timing logs
claude Feb 23, 2026
2bd1198
fix(docs): correct Cloud Run region from asia-northeast1 to us-central1
claude Feb 23, 2026
81fac9b
fix(deploy): update memory to 4Gi and add CLAUDE.md project notes
claude Feb 23, 2026
528dd5f
docs: update CLAUDE.md as handover document for next session
claude Feb 23, 2026
dbcd169
Add files via upload
mirai-gpro Feb 24, 2026
8435dac
feat(audio2exp): switch from streaming to batch inference mode
claude Feb 24, 2026
003dc11
docs: update deploy command to match successful production params
claude Feb 24, 2026
7994972
fix(audio2exp): implement WARMUP_TIMEOUT/ENGINE_LOAD_TIMEOUT env vars
claude Feb 24, 2026
19e0b16
docs: SDK Expression 52次元処理の徹底調査報告
claude Feb 25, 2026
0184988
fix(docs): correct wrong conclusion in SDK investigation report
claude Feb 25, 2026
5bd1830
docs: 確定事実と否定済み仮説の記録ファイル作成
claude Feb 25, 2026
c5a94ed
fix(docs): F3修正 — アバターの口は動いている(クオリティが低いだけ)
claude Feb 25, 2026
022ce2d
feat: add SDK runtime diagnostic script for expressionBSNum verification
claude Feb 25, 2026
66333b6
docs: document proven successful deploy command with actual logs
claude Feb 25, 2026
45f0a26
Add files via upload
mirai-gpro Feb 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Project Notes - 引継ぎ文書

## 現在の状況: audio2exp-service デプロイ(進行中)

### やったこと
1. audio2exp-service を修正し、再ビルド・再デプロイを実施
2. `--memory 2Gi` ではメモリ不足で3回失敗 → `4Gi` に増やして完走
3. デプロイ完走後のヘルスチェックで **NG** → 原因調査・対処が必要

### 現在のステータス
- **デプロイ**: 完走済み(メモリ4Gi)
- **ヘルスチェック**: NG(未解決)
- **次のアクション**: ヘルスチェックNG原因の調査・修正・再デプロイ

### ルール
- 推測で回答せず、必ず会話ログ・ファイル・記録を確認してから回答すること
- 確定していない中途半端な情報を書き出さないこと
145 changes: 145 additions & 0 deletions docs/CONFIRMED_FACTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# 確定事実と否定済み仮説

> **作成日**: 2026-02-25
> **目的**: 後続セッションのClaudeが事実を無視して妄想しないための拘束ファイル
> **ルール**: このファイルに記載された事実を覆すには、新たなブラウザランタイムエビデンスが必要。推測で否定してはならない。

---

## 確定事実(実証済み)

### F1. 音声再生は正常に動作している

- **STT→LLM→TTS パイプライン**: 正常動作。ユーザーの音声入力がSTTでテキスト化され、LLM(Gemini 2.0 Flash)が応答を生成し、Google Cloud TTSが音声合成し、フロントエンドで再生される
- **TTSの読み上げ**: フロントエンドのチャットテキストに表示された内容がTTSで読み上げられている(ユーザー実証済み)
- **TTS synthesize エンドポイント**: HTTP 200 OK を返している(`claude_log_20260224.txt` 6374行)
- **実証回数**: 2026年2月24日だけでも20回以上のテスト実行、ブラウザコンソールログで裏付け済み
- **バグ修正履歴**: TTS再生に関する既知バグは修正済み
- `2e16f78`: テキスト入力時にTTS再生されない問題を修正
- `4332c8f`: autoplay deadlock → STT停止の問題を修正(play-and-waitパターン修正)

### F2. A2E 52次元Expressionデータはフロントエンドのバッファまで到達している

- **ブラウザコンソールログ**(`claude_log_20260224.txt` 6296-6303行):
```
LAMAvatar.astro:543 [LAM Avatar] Added 311 frames to buffer (total: 311) at 60fps
concierge-controller.ts:443 [Concierge] Expression: 156→311 frames (30→60fps)
| jaw: max=0.422 avg=0.071 | funnel: max=0.296 | smile: max=0.122

LAMAvatar.astro:543 [LAM Avatar] Added 617 frames to buffer (total: 617) at 60fps
concierge-controller.ts:443 [Concierge] Expression: 309→617 frames (30→60fps)
| jaw: max=0.456 avg=0.073 | funnel: max=0.107 | smile: max=0.183
```
- **データの中身**: 空ではない。jawOpen最大0.456、mouthFunnel最大0.296など、有効な値が入っている
- **フレーム数**: 複数チャンクで311, 617フレーム。A2Eバックエンドから正常にデータが返っている
- **フレームレート変換**: 30fps→60fpsの補間処理がフロントエンドで実行されている
- **データフロー**: `audio2exp-service` → `gourmet-support (TTS応答に同梱)` → `concierge-controller.ts (applyExpressionFromTts)` → `lamAvatarController.queueExpressionFrames()` → LAMAvatar バッファ

### F3. アバターの口は動いている(タイミングもほぼ正しい)

- **ユーザー実証済み**: アバターの口はそれっぽく動いており、TTS音声とのタイミングもほぼ合っている
- **つまり**: バッファ→SDK `getExpressionData()` →頂点シェーダーのパイプライン全体が繋がって動作している
- **問題はクオリティ**: 動いてはいるが、リップシンクの質が低い(F9参照)

### F4. skin.glb に51個のARKit morph targetが正常に格納されている

- **検証方法**: pygltflib で `concierge_fne.zip` 内の `skin.glb` を直接解析
- **結果**: 51個のmorph target(sparse accessor形式)
- **各targetに実データあり**: 700〜7,287個の非ゼロ頂点デルタ
```
jawOpen : sparse count=2755
eyeBlinkLeft : sparse count=4491
cheekPuff : sparse count=7287
mouthShrugLower : sparse count=3539
(全51 target確認済み — 全てデータあり)
```
- **欠損**: `tongueOut`(52個中の1個のみ)。リップシンクには影響なし
- **エビデンスファイル**: `docs/INVESTIGATION_SDK_EXPRESSION_52DIM.md` §1

### F5. SDK(gaussian-splat-renderer-for-lam@0.0.9-alpha.1)はsparse accessorに対応している

- **検証方法**: npmパッケージを展開し、内蔵Three.js GLTFLoader(r173ベース)のソースコードを直接確認
- **該当コード**: `accessorDef.sparse !== undefined` のブランチでsparseIndices/sparseValuesを展開する実装あり
- **エビデンスファイル**: `docs/INVESTIGATION_SDK_EXPRESSION_52DIM.md` §2.5

### F6. SDKの `expressionBSNum` はmorph target数から設定される

- **SDK内部コード**:
```javascript
this.expressionBSNum = this.flameModel.geometry.morphAttributes.position.length;
this.material.uniforms.bsCount.value = this.expressionBSNum;
```
- **理論値**: skin.glbに51個のmorph targetがあれば `expressionBSNum = 51`
- **注意**: ブラウザ実行時に実際に51になっているかは**未検証**(ランタイム確認なし)
- **エビデンスファイル**: `docs/INVESTIGATION_SDK_EXPRESSION_52DIM.md` §2.3

### F7. SDKのExpression処理フロー(コードレベルで確認済み)

- **毎フレームの処理**:
1. `getExpressionData()` コールバック → `{ jawOpen: 0.45, mouthFunnel: 0.12, ... }`
2. `setExpression()` → `splatMesh.bsWeight = expressionData`
3. `updateBoneMatrixTexture()` → `morphTargetDictionary[name]` でindex取得 → GPUテクスチャにパック
4. Vertex Shader → `for(int i = 0; i < bsCount; ++i)` ループでblendshape適用
- **名前ベースのマッピング**: SDKは配列indexではなく名前で辞書検索。順序非依存
- **エビデンスファイル**: `docs/INVESTIGATION_SDK_EXPRESSION_52DIM.md` §2.2, §2.4

### F8. audio2exp-service は Cloud Run にデプロイ済み、ヘルスチェック通過

- **URL**: `https://audio2exp-service-417509577941.us-central1.run.app`
- **ヘルスチェック**: `engine_ready: true`(`claude_log_20260224.txt` 内で確認)
- **メモリ**: 4Gi(2Giでは3回OOM、4Giで完走)
- **出力**: 52次元ARKit blendshape @ 30fps

### F9. リップシンクのクオリティが低い(日本語・英語とも)

- **ユーザー実証済み**: 日本語も英語も同様にクオリティが低い
- **言語差なし**: A2Eモデル(Wav2Vec2ベース)は音響ベースで動作するため、言語による品質差は小さい。両方とも低いのは言語の問題ではなくパイプライン全体の問題

### F10. ブラウザログの `_Vector3 12248829 0` の `0` は `expressionBSNum` ではない

- **正体**: SDK内部の `console.log(cameraPos, backgroundColor, alpha)` の出力
- `_Vector3` = cameraPos(Vector3オブジェクト)
- `12248829` = backgroundColor(parseInt結果)
- `0` = alpha値(透明度パラメータ)
- **エビデンスファイル**: `docs/INVESTIGATION_SDK_EXPRESSION_52DIM.md` §3

---

## 否定済みの仮説(再提示禁止)

| # | 仮説 | 否定理由 | エビデンス |
|---|------|----------|-----------|
| H1 | 音声が再生されていない / audioフィールドが空 | **音声は正常再生されている。** STT→LLM→TTSパイプラインは動作し、チャットテキストがTTSで読み上げられている。20回以上のテストで実証済み | ユーザー実証、コミット `2e16f78` `4332c8f` |
| H2 | skin.glbにmorph targetがない | 51個のmorph targetが実データ付きで格納されている | pygltflib解析、`INVESTIGATION_SDK_EXPRESSION_52DIM.md` §1 |
| H3 | SDKがsparse accessorに非対応 | Three.js r173 GLTFLoaderに対応コードあり | `INVESTIGATION_SDK_EXPRESSION_52DIM.md` §2.5 |
| H4 | `expressionBSNum = 0` | `0`はalpha(透明度)パラメータ | `INVESTIGATION_SDK_EXPRESSION_52DIM.md` §3 |
| H5 | A2Eバックエンドがデータを返していない | 311, 617フレームがフロントエンドバッファに到達 | `claude_log_20260224.txt` 6296-6303行 |
| H6 | アバターの口が動いていない | **口は動いている。** タイミングもほぼ正しい。問題は動かないことではなくクオリティが低いこと | ユーザー実証済み |

---

## 未解決の問題(原因未特定)

### 核心的な問題

**リップシンクのクオリティが低い。** パイプライン全体は繋がって動いている(音声再生、Expressionデータ到達、口の動き、タイミング全てOK)が、口の動きの質が不十分。

### 品質が低い原因の候補(要調査)

以下は仮説ではなく、「まだ検証していない領域」の列挙。

1. **A2Eモデルの出力品質**: Wav2Vec2 → A2E Decoderの出力するblendshape係数自体の精度。jawOpen max=0.456 は十分か、他のblendshapeの値域は適切か
2. **blendshape増幅パラメータの調整**: `concierge-controller.ts` の `MOUTH_AMPLIFY` 係数が最適かどうか
3. **フレーム補間の品質**: 30fps→60fps線形補間が滑らかさに十分か
4. **SDKの `expressionBSNum` のランタイム値**: 理論上51だが、ブラウザで実測していない。仮に少ない数値だと一部blendshapeが無視される
5. **A2Eモデルが口以外のblendshapeを十分に活用しているか**: 眉、目、頬などの表情パラメータが生成されているか

---

## このファイルの使い方

1. 新しいセッションの最初に必ずこのファイルを読む
2. §否定済みの仮説 に記載された仮説を再提示しない
3. §未解決の問題 の検証から作業を開始する
4. 新たな事実が判明したら、このファイルを更新する
5. **推測で事実を覆さない。エビデンスがなければ「不明」と書く**
201 changes: 201 additions & 0 deletions docs/INVESTIGATION_SDK_EXPRESSION_52DIM.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
# 調査報告: LAM WebGL SDK — 52次元 Expression Blendshape 処理

調査日: 2026-02-25

## 結論

**公式ModelScope SpaceのZIPに不備はない。** skin.glb に51個のARKit morph targetが正常に格納されており、SDKは正しく読み取れる設計になっている。

---

## 1. 公式ZIPの実データ検証

### ZIP構成 (`concierge_fne.zip` — ModelScope Space生成)

| ファイル | サイズ | 内容 |
|---------|-------|------|
| `skin.glb` | 3.6MB | 20,018頂点, 262ボーン, **51 morph targets (sparse)** |
| `offset.ply` | 1.3MB | 20,018 Gaussians × 17属性 (pos/color/opacity/scale/rotation) |
| `animation.glb` | 2.2MB | 12個のボーンアニメーション (idle, speak, think等) |
| `vertex_order.json` | 209KB | 頂点順序マッピング |

### skin.glb morph target 詳細

- **51個** のARKit blendshape(52個中 `tongueOut` のみ欠損)
- 全て **sparse accessor** 形式(glTF2.0仕様準拠、効率的な格納)
- 各targetに700〜7,287個の非ゼロ頂点デルタ(実データ確認済み)

```
mouthShrugLower : sparse count=3539
jawOpen : sparse count=2755
eyeBlinkLeft : sparse count=4491
cheekPuff : sparse count=7287
(全51 target確認済み — 全てデータあり)
```

### 欠損blendshape

| 名前 | 状態 | 影響 |
|------|------|------|
| `tongueOut` | 欠損 | 舌を出す表情のみ不可。リップシンクには影響なし |

---

## 2. SDK ソースコード解析 (`gaussian-splat-renderer-for-lam@0.0.9-alpha.1`)

npmパッケージを直接展開して確認。

### 2.1 アーキテクチャ: 2つのモード

```javascript
// SDK内部 (line ~152272)
var useFlame = "false"; // ← ハードコード
renderer.useFlame = (charactorConfig.useFlame == "false") ? false : true;

if (renderer.viewer.useFlame == true) {
yield renderer.loadFlameModel(fileName, motionConfig);
// → skin.glb + lbs_weight_20k.json + flame_params.json + vertex_order.json + bone_tree.json
} else {
yield renderer.loadModel(fileName, animationConfig, motionConfig);
// → skin.glb + animation.glb + vertex_order.json ← こちらが使われる
}
```

**現在のSDKは `useFlame=false` がハードコード**されている。OAC ZIPはこのモード用。

### 2.2 Expression Blendshape 処理フロー (useFlame=false)

```
[毎フレーム]
1. getExpressionData() callback
→ { jawOpen: 0.45, mouthFunnel: 0.12, ... } (52次元)

2. setExpression()
→ splatMesh.bsWeight = expressionData (名前→重み辞書)

3. updateBoneMatrixTexture()
→ morphTargetDictionary[name] でindexを取得
→ boneTexture[idx + bonesNum*16] = weight (GPUテクスチャにパック)

4. Vertex Shader (GPU)
for(int i = 0; i < bsCount; ++i) {
float weight = boneTexture[i / 4 + 5 * 4][i % 4];
splatCenter += weight * flameModelTexture[i]; // BS基底 × 重み
}
```

### 2.3 expressionBSNum の設定タイミング

```javascript
// setupDataTextures() — offset.ply ロード後に実行
this.expressionBSNum = this.flameModel.geometry.morphAttributes.position.length;
this.material.uniforms.bsCount.value = this.expressionBSNum;
```

この時点で `flameModel` は既にskin.glbから読み込み済みなので、
morph targetが正常にロードされていれば `expressionBSNum = 51`。

### 2.4 buildModelTexture — morph target をGPUテクスチャにパック

```javascript
// 各morph targetの頂点データをflatに連結 → 4096x2048 テクスチャへ
morphTargetNames.forEach((name, newIndex) => {
const originalIndex = flameModel.morphTargetDictionary[name];
var bsMesh = flameModel.geometry.morphAttributes.position[originalIndex];
shapedMeshArray = shapedMeshArray.concat(Array.from(bsMesh.array));
});
// ベースメッシュも追加
shapedMeshArray = shapedMeshArray.concat(Array.from(shapedMesh));
```

**→ SDKは morph target名前ベースで辞書検索。順序非依存。**

### 2.5 Three.js GLTFLoader — sparse accessor 対応済み

```javascript
// SDK内蔵のGLTFLoader (Three.js r173ベース)
if ( accessorDef.sparse !== undefined ) {
const sparseIndices = new TypedArrayIndices(bufferViews[1], ...);
const sparseValues = new TypedArray(bufferViews[2], ...);
for (let i = 0; i < sparseIndices.length; i++) {
bufferAttribute.setX(sparseIndices[i], sparseValues[i * itemSize]);
// ... setY, setZ
}
}
```

**→ sparse accessor は正しく展開される。**

---

## 3. ブラウザログの再解釈

```
gaussian-splat-renderer-for-lam.js:62550 download completed: ArrayBuffer(4094984)
gaussian-splat-renderer-for-lam.js:62588 _Vector3 12248829 0
```

この `_Vector3 12248829 0` は SDK内部の `console.log(cameraPos, backgroundColor, alpha)` の出力:
- `_Vector3` = cameraPos (Vector3オブジェクト)
- `12248829` = backgroundColor (parseInt結果)
- `0` = alpha値

**`0` は `expressionBSNum` ではなく、透明度(alpha)パラメータ。**

---

## 4. 調査結論

**SDK・ZIP・バックエンドの全レイヤーで技術的な問題は確認されなかった。**

- skin.glb: 51個のARKit morph target(sparse accessor、実データ確認済み)
- SDK: `expressionBSNum` は morph target数から正しく設定される
- SDK: Three.js GLTFLoader r173 が sparse accessor を正しく展開
- SDK: 頂点シェーダーが `for(i < bsCount)` ループで blendshape を適用
- audio2exp-service: 完成・Cloud Runデプロイ済み、ヘルスチェック通過

**ブラウザログ `_Vector3 12248829 0` の `0` は `expressionBSNum` ではなく、`alpha`(透明度)パラメータ。**

---

## 5. 副次的な発見

### 5.1 flame_arkit.py assertion バグ(本番影響なし)

```python
# flame_arkit.py:108
assert expr_params != 52, "The dimension of the ARKIT expression must be equal to 52."
# ↑ != は == であるべき。ただしこのモデルはOACパスでは使われないため本番影響なし。
```

### 5.2 h5_rendering パス(無効化済み)

`app_lam.py:42` で `h5_rendering = False`。このパスは:
- 100個のFLAME標準expression(52次元ARKitではない)
- `lbs_weight_20k.json` + `bone_tree.json` + `flame_params.json` を生成
- `useFlame=true` モード用
- 現在無効化

### 5.3 OACパスのZIP生成

`app_lam.py:304-342`:
- template FBX(ARKit blendshape内蔵)からskin.glbを生成
- animation.glb は固定ファイルをコピー
- **ZIPにExpression基底データは正しく含まれる**(template FBXに51個のblendshape内蔵済み)

---

## 6. 次のアクション(SESSION_HANDOFF.md §8 準拠)

**最優先: iPhone SEでの実機検証**

1. `gaussian-splat-renderer-for-lam` をnpm installしてミニマルHTML作成
2. ModelScope SpaceでアバターZIP生成
3. iPhone SE実機 (Safari) でFPS計測
4. 30FPS出るなら Approach A (LAM WebGL SDK) で進行
5. 出ないなら Approach B (Three.js + GLBメッシュ) に切り替え

**並行: エンドツーエンド統合テスト**
- gourmet-sp + gourmet-support + audio2exp-service の結合テスト
- TTSレスポンスに `expression: { names, frames, frame_rate }` が含まれることを確認
- フロントエンドの `getExpressionData()` が非空データを返すことを確認
Loading