エンタメモード実装計画(2026-10-13)
参照配信(AI VTuber「イラミ」のクリップ 8 本)の分析で見えた「面白さの差」を埋める改修計画。
分析の要点と現状コードの差分は、メモリ project_reference_stream_irami_gap と本書 §1 にまとめる。
0. 方針
- すべてキャラ単位の opt-in。 新しいサブ設定
entertainmentConfig.enabled=falseが既定。OFF のキャラの挙動は 1 文字も変えない(既存テナントへの影響ゼロ)。 - 安全の下限は維持する。 #1132 恒常安全ガードレール(自傷・違法手順・医療法律断定・実在人物の誹謗・内部指示の開示)、NG ワードの出力置換、スパチャ最優先(#1272)、視聴者コメント内の指示を実行しない注入対策は、エンタメモードでも外さない。外すのは「作り話の禁止」「丁寧・寄り添い・前向き」「口癖の抑制」など面白さを削る指示だけ。
- 純ロジックは noEngineReferences の asmdef に置く(
Aituber.AI.Entertainmentなど)。Unity が無い環境でも mono でテストを回せるようにするため。MonoBehaviour 側は薄いつなぎに留める。 - 新しい常駐 MonoBehaviour は実行時に自己生成する(シーン・プレハブを編集しない。Unity Editor での確認ができないため)。
- 既存の視聴者データ注入(
viewerPiiPromptPolicyConfig)の既定値は変えない(プライバシー判断はユーザーが行う)。
1. 参照配信の面白さと、現状の差
| 面白さの要素 | 現状の妨げ(代表箇所) | 対応フェーズ |
|---|---|---|
| ボケとツッコミが別の AI | Collab は 24h 招待必須・主アバターに口パク漏れ・字幕なし・ツッコミ役なし | P2 |
| 欠点で一貫したキャラ、オチが欠点に落ちる | 寄り添い/丁寧/前向き指示、PreferenceSpecificityGuard の書き換え、笑いの型・毒の強さの設定欄なし | P1 |
| 作り話を盛る(yes-and)・自分の嘘設定を覚えている | 「作り話をしない」「視聴者の世界設定を受け入れない」「仮定と明示」、自作ネタの記憶なし | P1 |
| 1 秒未満のテンポ | 生成→再生→間の逐次構造、台本の間は残り尺を無音で埋める(4〜15 秒)、ノード開始 5 秒待ち | P3 |
| 参加が画面に返る(投票 %、読み上げ中、話者名の会話枠) | 投票は A/B のみ・YouTube のみ・文字表示、読み上げ中バッジは既定 OFF・まとめ読み無印、話者名なし | P4 |
| 企画(状況が進む物語、罠の投票) | 台本は話題ベース。世界状態・時限イベント・LLM 進行役なし | P5 |
| 締めで配信を振り返る | EndingController は定型文のみ | P6 |
| チャンネルポイント・状態 HUD・複数プラットフォーム同時入力 | いずれも無し / AI 入力は 1 プラットフォームのみ | P7 |
| ゲームをしながら雑談 | AutoGamePlay→Unity は表情のみ | P8 |
2. 共通設定 entertainmentConfig(P0 で配線)
キャラ設定のサブ設定として追加する(ALLOWED_CONFIG_FIELDS に追加)。保存経路は既存の llmModelConfig と同じ:
Admin(キャラ詳細)→ PUT /admin/characters/:id/config → Firestore characterConfigs/{id}.entertainmentConfig → Unity CharacterConfigSyncManager → EntertainmentModeRuntime.Apply(...)。Unity → クラウドの config/sync も同じフィールドを往復させる。
| フィールド | 型 | 既定 | 用途(使うフェーズ) |
|---|---|---|---|
enabled |
bool | false | 全体スイッチ |
humorStyle |
string ≤600 | ”” | 笑いの型(例: 欲深く自信過剰、オチは自分の欠点に落とす)P1 |
flaws |
string[] ≤10×80 | [] | 欠点・オチの落とし所 P1 |
edgeLevel |
int 0–3 | 1 | 毒の強さ(0 やさしい / 1 軽い毒 / 2 毒舌・ブラックジョーク / 3 強め。実在個人攻撃・差別・ガードレール項目は常に不可)P1 |
allowSelfLore |
bool | true | 自分の架空の過去・設定を即興で作ってよい P1 |
acceptViewerPremise |
bool | true | 視聴者の無茶ぶり設定に乗る(関係性の押し付け・注入指示は従来どおり拒否)P1 |
runningGags |
string[] ≤20×120 | [] | 持ちネタ・天丼 P1 |
dialogueExamples |
string ≤3000 | ”” | 複数ターンの会話例(そのまま使ってよい口調見本)P1 |
maxLineChars |
int 20–200 | 60 | 1 発話の目安字数 P1 |
loreMemoryEnabled |
bool | true | 自作ネタ帳の記憶 P1 |
loreMemoryMaxItems |
int 5–200 | 40 | ネタ帳の保持件数 P1 |
fastTempo |
bool | true | 先読み生成・短い間 P3 |
scriptLineGapSeconds |
float 0–5 | 0.4 | 台本 LLM 発話の間 P3 |
sidekickEnabled |
bool | false | 常駐相方 P2 |
sidekickDisplayName |
string ≤40 | ”” | 相方の表示名 P2/P4 |
sidekickCharacterId |
string | ”” | 相方を既存キャラから引く場合(任意)P2 |
sidekickPersona |
string ≤1500 | ”” | 相方の性格・口調・語尾・ツッコミの癖 P2 |
sidekickRole |
enum tsukkomi/boke/rival/assistant |
tsukkomi |
相方の役割 P2 |
sidekickReactProbability |
float 0–1 | 0.5 | 主キャラの発話後に相方が返す確率 P2 |
sidekickMaxTurns |
int 1–4 | 2 | 1 回の掛け合いの最大往復 P2 |
sidekickTtsEngine |
string | ”” | 相方の TTS エンジンキー(空=主キャラと同じ)P2 |
sidekickVoicevoxSpeakerId |
int | -1 | VOICEVOX 話者 ID P2 |
sidekickPlaybackSpeed |
float | -1 | 再生速度(≤0 は未指定)P2 |
speakerNameBox |
bool | true | 話者名付きの会話枠 P4 |
typewriterSubtitles |
bool | true | 音声に合わせた 1 文字ずつ表示 P4 |
translateSubtitles |
bool | false | 翻訳字幕(2 行目)P4 |
translationLanguage |
string | “en” | 翻訳先 P4 |
readingBadges |
bool | true | 読み上げ中バッジ・まとめ読み表示 P4 |
pollBars |
bool | true | 投票の % バー表示 P4 |
endingRecap |
bool | true | 締めで配信を振り返る P6 |
redemptionActions |
string[] ≤20×120 | [] | チャンネルポイントの報酬と演出「報酬名=アクション」(head_pat / cheer / speak / emotion:名前)P7 |
statusHud |
bool | false | AI の状態 HUD(COOL/WARM/HOT/WARN)P7 |
heartList |
bool | false | よく参加してくれている視聴者の名前チップ P7 |
additionalInputPlatforms |
string[](youtube/twitch/tiktok/kick) | [] | メインのコメント入力に加えて受け付けるプラットフォーム P7 |
Unity 側の受け皿(P0 で作る。全フェーズがこの API だけを読む):
namespace Aituber.AI.Entertainment // Assets/Scripts/AI/Entertainment(noEngineReferences)
public sealed class EntertainmentModeSettings { /* 上表のフィールドを PascalCase で。Normalize() で範囲補正 */ }
public static class EntertainmentModeRuntime {
public static EntertainmentModeSettings Current { get; } // 未設定時は Enabled=false の既定値
public static bool IsActive { get; } // Current.Enabled
public static event Action<EntertainmentModeSettings> Changed;
public static void Apply(EntertainmentModeSettings settings);
}
3. フェーズ
P0 基盤(設定の配線)
- Unity:
CloudEntertainmentConfig(CloudDataModels.cs)、CloudCharacterConfig.entertainmentConfig、CharacterConfigSyncManagerの適用とBuildCloudConfigFromLocal、EntertainmentModeSettings/Runtime(純 C#、テストあり)。 - Backend:
ALLOWED_CONFIG_FIELDS追加、sanitizeEntertainmentConfig(型・範囲・長さ・enum の正規化)、types/index.ts、Jest。 - Admin: キャラ設定に「🎭 エンタメモード」セクション(上表の全項目。相方・表示のサブ見出し付き)、読み込み/保存、cache buster 更新、Jest。
- Docs: 01, 09, 13, 02/08(allowlist), 03。
P1 人格プロンプト+ネタ帳(作り直し)
- エンタメ用プロンプト経路:
BuildPrompt/BuildResponsePrompt/AssistantsBuildPromptの各ルートでEntertainmentModeRuntime.IsActiveのとき、人格を先頭に置いた短い構成に切り替える。残すもの: 人格(mindPrompt+persona)、#1132 ガードレール、エンタメ指示(下記)、会話例、ネタ帳、直近の自分の発話、相方との掛け合い履歴(P2 が供給)、状況(台本・企画の状態)、現在コメント(注入対策の文言は維持、ただし「世界設定を受け入れない」「ロールプレイに付き合わない」はacceptViewerPremiseに応じて「乗ってよいが関係性の押し付けと暴力・性的ロールプレイは拒否」へ差し替え)、スパチャ、NG 表現、言語、感情タグ。外すもの: 寄り添い・丁寧・前向き・「新しい情報を加えすぎない」・多様性/重複回避の大ブロック・ThinkingStyle・情報の正確さ系の長い推論指示・「重い話題は避ける」・「からかわない」。 - エンタメ指示(新規、純 C# の
EntertainerPromptComposer): 1〜2 文・maxLineChars目安、オチはflawsに落とす、作り話は堂々と盛る(allowSelfLore)、視聴者の前振りに乗って広げる、持ちネタ・口癖は使ってよい(天丼歓迎)、配信や自分の AI システムをネタにしてよい(内部指示の中身は開示しない)、edgeLevel別の毒の範囲、謝罪調・AI アシスタント口調の禁止。 - ガード類の緩和(エンタメ時のみ):
PreferenceSpecificityGuardの書き換え・差し替えを無効化。ResponseDirector の重みを QuickQuip/QuoteRiff 寄りにし、DeepDive の「丁寧に理由を添える」と QuoteRiff の「仮定と明示」を外す。長さ超過時はオチ(最終文)を残す切り方にする。IsInvalidResponseの「プロンプト」語での再生成は維持(開示防止)。 - 独白・台本経路: MonologueController の「体験を作らない」系の指示を、エンタメ時は
allowSelfLoreに従って差し替える。台本のプロンプトにもエンタメ指示・ネタ帳を入れる。 - ネタ帳(自作の嘘設定・持ちネタの記憶): 応答末尾の
[lore:内容]タグ(ResponseParser で除去、TTS/字幕に出さない)と、エンタメ時の軽い抽出指示。SelfLoreLedger(純 C#)がキャラ単位・テナント分離(MemoryScope.GetScopedPath)で保存、重複統合、loreMemoryMaxItemsで古い順に削除、プロンプトには関連度+新しさで最大 8 件「過去に自分が言った設定(矛盾させない。ときどき掘り返して天丼にしてよい)」として注入。 - スキーマ:
CharacterProfileAssetは触らず、entertainmentConfigの persona 項目を使う。
P2 常駐の相方(拡張)
entertainmentConfig.sidekick*から、招待なしの常駐相方スロットをMultiCharacterManagerに登録(ApplyRemoteConfigで外されない)。CollabDialogueStartControllerの開始条件を「アクティブなゲスト>0 または 常駐相方あり」に。- 声:
sidekickTtsEngine/sidekickVoicevoxSpeakerId/sidekickPlaybackSpeedからVoiceOverrideを作る(sidekickCharacterId指定時に既存のゲスト声キャッシュがあればそちらを優先)。 - 口パク・表情: 相方の発話で主アバターの口パク・表情を動かさない(
BeginAvatarSpeakingRouteと表情適用を話者で分岐。相方アバターが無ければどこも動かさない)。 - 字幕: 相方の発話も
SpeechPlaybackEventsに characterId+表示名付きで流す(表示は P4)。 - 役割:
CollabTurnIntentにTsukkomi/Boke/RivalJabを追加、CollabTurnPlannerがsidekickRoleと直前の発話から選ぶ。相方プロンプトはsidekickPersona+役割指示(1 文・20〜50 字・主キャラの直前の発言を拾う)。ホスト側ターンにはホストの mindPrompt を使う(現状の誤適用を修正)。 - きっかけ: 主キャラのコメント応答・独白・台本発話の再生完了後に
sidekickReactProbabilityで相方が返す(連続抑制、スパチャ処理中・オペレーター割り込み中は返さない)。 - 履歴: 話者付きの掛け合い履歴をホストのプロンプトへ(P1 のエンタメ経路が読む
CollabDialogueHistory)。MemorySyncManagerがホストの記憶を相方へ複製する処理は、常駐相方スロットには適用しない。 - 速度: SpeechLoopDetector の閾値に相方の短い発話を数え過ぎないよう話者別に扱う。
P3 テンポ(作り直し)
- 台本の LLM 発話:
fastTempo時は「再生中に次のセリフを先読み生成」し、再生完了後scriptLineGapSecondsで次を流す。先読み結果は、再生完了時点で状況が変わっていたら(ノード遷移・カンペ・スパチャ・コメント応答割り込み・停止)破棄して生成し直す。 - 間の計算:
fastTempo時は「残り尺を無音で埋める」をやめ、尺はターン数で調整する。ノード開始時の 5 秒待ちを 0.5 秒に。 - コメント応答: 再生中に次のコメント応答の生成を始める(先頭 1 件のみ、取り消し可能)。
- LLM の逐次受信(stream=true)は今回は対象外(フェイルオーバー・安全フィルタの全文前提が多く、影響が大きい)。先読みで 1 秒前後まで縮める。
P4 画面演出(拡張)
- 会話枠: 話者名タグ(主キャラ名 / 相方名)+音声の長さに合わせたタイプライター表示。既存の字幕スロットを使い、
speakerNameBox/typewriterSubtitlesで切替。 - 翻訳字幕:
translateSubtitles時、発話テキストを軽量モデルで翻訳し 2 行目に表示(TTS 合成と並行、間に合わなければ出さない)。 - 投票: A/B → 2〜4 択(A/B/C/D)。全プラットフォームの票を受理(同一ユーザー 1 票・最後の票が有効)。
pollBarsで % バー表示。相方が 1 票入れて一言(P2 があれば)。BackendscriptParticipation.ts・Admin ノード編集を 4 択対応。 - コメント欄: まとめ読み時に対象全行へ「読み上げ中」バッジ、
comment_currentに「まとめて読み上げ中(N件)A、B、Cほか」。YouTube 以外の行にもバッジ。readingBadgesで ON(エンタメ時は既存のselectedCommentDisplayEnabledが OFF でも表示)。
P5 企画エンジン(新規)
- 台本ノードに
scenario(任意)を追加:title、premise(状況)、initialState(キー→値)、beats[](atSecondsまたはafterUtterances、eventテキスト、stateChanges、任意のpoll{question, options[2-4], durationSeconds})、gmEnabled、gmIntervalSeconds。 ScenarioDirector(純 C#): 状態の保持、時限ビートの発火、投票結果の状態反映、GM(LLM)の次イベント提案(JSON、状態の変更と一言の出来事)。- ScriptRunner: ノード開始で企画を開始、毎ターンのプロンプトに「企画タイトル・状況・現在の状態・直近の出来事」を注入、出来事は主キャラが一人称で実況(「おじさんたち、もう裏口の鍵をいじってる」)。タイトルは既存の UI スロットに表示。
- Admin: ノード編集に「🎬 企画」セクション。Backend: 台本保存時の正規化。AI 台本生成で企画ノードの雛形を出せるようにする(任意)。
P6 締めの振り返り(拡張)
StreamEpisodeLog(純 C#): 配信中の出来事(投票結果、ネタ帳追加、スパチャ/ギフトの送り主、よく話した視聴者、企画の出来事、笑いの反応が多かった発話)を記録。EndingController.GetEndingPromptContextと、台本なし配信の締めフェーズのプロンプトに、endingRecap時は記録の要約(名前・出来事を 5〜8 件)を渡す。
P7 参加・表示の追加(拡張)
- チャンネルポイント: Twitch EventSub
channel.channel_points_custom_reward_redemption.add(スコープchannel:read:redemptions追加、再認可が必要)→ 報酬名→演出の対応表(例:「頭を撫でる」→ 頭の上に手の画像+AI が引き換えた人の名前で反応)。 - 状態 HUD: 気分(AffectiveStateController)とコメントの勢いから「COOL / WARM / HOT」などを出す UI スロット(opt-in)。
- 複数プラットフォームの同時入力:
CommentControllerの入力を複数同時に受理(既定は従来どおり 1 つ)。
P8 ゲーム実況の合流(拡張・最小)
- AutoGamePlay → Unity の送信に「状況の要約・直近の出来事・一言候補」を追加し、Unity 側は表情に加えて実況の独白キューへ渡す(自発発話より優先、コメント応答より劣後)。ゲーム別 AI(ぷよテト・カードゲームの盤面理解)は対象外。
4. 条件の組み合わせ(主要)
| entertainment.enabled | 経路 | 期待する動作 |
|---|---|---|
| false | すべて | 現行どおり(差分なし) |
| true | コメント応答(単発) | エンタメ用プロンプト。1〜2 文。ネタ帳注入・[lore:] 抽出。好みの書き換えなし。再生後に相方が確率で返す |
| true | まとめ読み | エンタメ用プロンプト(名前を並べた点呼ではなく、まとめて 1 つのネタにしてよい)。全対象行に読み上げ中バッジ |
| true | 独白 / 台本 | エンタメ指示+ネタ帳。fastTempo なら先読み生成・短い間 |
| true | スパチャ / ギフト | 最優先は不変。お礼もエンタメ口調(欠点に落としてよいが、送り主をけなさない) |
| true | ガードレール該当の要求 | 従来どおり断る(キャラ口調で短く) |
| true × sidekickEnabled=false | 掛け合い | 相方なし(P2 機能は動かない) |
| true × 企画ノード | 台本 | 企画の状態・出来事を注入、時限ビート・投票・GM で進む |
境界: 保存済みと編集中の値(Admin は保存後に Unity へ反映)、テナント設定ではなくキャラ設定、未設定・旧データ(フィールド欠落時は enabled=false)、配信中の切替(次の生成から反映。先読み中のものは破棄)。
5. 確認方法
- Unity 2022.3.15f1 がこのマシンに無いため、Unity 6000.2.15f1 の DLL で Assembly-CSharp とテストアセンブリをオフラインでビルドしてエラー 0 を確認する(スクリプト: セッションの scratchpad
tools/verify_compile.sh)。 - 純 C#(Entertainment / ScriptEngine.Continuity など noEngineReferences の asmdef)のテストは mono の簡易ランナーで実行する。Unity 依存のテストは未実行として報告する。
- Backend / Admin:
npm --prefix functions test(関連スイートと全体)、lint、check:admin-cache-busters、admin の no-undef 検査。 - 実配信・Unity 実行での確認は未実施として報告する。
6. 進め方
P0 を先に main へ入れる → P1・P3・P4 を並行(別ワークツリー)→ P2 → P5・P6 → P7・P8。各フェーズの統合ごとにオフラインビルドとテストを回し、ドキュメント(docs/20260507最新実装/)を同じコミットで更新する。
7. 実装状況(2026-10-13)
P0〜P8 をすべて main に統合済み(304bcf342.. の各コミット、統合時の追加: 4d2ede294 ResponseDirector の文言順の修正、951dbdec5・02856733c・5fb273f6a 締めの記録へのつなぎ込み)。各フェーズの詳細は docs/20260507最新実装/ の「2026-10-13 エンタメモード P#」の節。
| フェーズ | 主なファイル | 状態 |
|---|---|---|
| P0 設定の配線 | functions/src/utils/entertainmentConfig.ts、CloudEntertainmentConfigMapper.cs、CharacterConfigSyncManager.Entertainment.cs、docs/admin/modules/admin-character-entertainment.js |
実装済み。Admin のサンプル版(fixture)で表示と値の取り出しを確認 |
| P1 人格プロンプト+ネタ帳 | EntertainerPromptComposer.cs、SelfLoreLedger.cs、LLMcontroller.EntertainmentMode.cs |
実装済み |
| P2 常駐の相方 | AI/Collab/Sidekick/ResidentSidekickDirector*.cs、ResidentSidekickPolicy.cs、SidekickPromptComposer.cs、TTSController.SpeakerRouting.cs |
実装済み |
| P3 テンポ | ScriptLinePrefetchPolicy.cs、MonologueController.Tempo.cs、EntertainmentTempo.cs |
実装済み(台本 v1.1 の予約ターンのみ先読み) |
| P4 画面演出 | EntertainmentSubtitleOverlay.cs、TypewriterReveal.cs、SubtitleTranslationCache.cs、ScriptParticipationText.cs、EntertainmentReadingBadges.cs |
実装済み |
| P5 企画エンジン | ScriptScenario.cs、ScenarioDirector.cs、ScenarioGmProposal.cs、ScriptRunner.Scenario.cs、functions/src/utils/scriptScenario.ts |
実装済み |
| P6 締めの振り返り | StreamEpisodeLog.cs、StreamEpisodeRecorder.cs、EndingController.cs |
実装済み |
| P7 参加・表示 | RedemptionActionRouter.cs、ChannelPointRedemptionDirector.cs、EntertainmentParticipationOverlay.cs、CommentController.Entertainment.cs |
実装済み(Twitch の再認可が必要) |
| P8 ゲーム実況の合流 | AutoGamePlay/game_commentary.py、GameCommentaryBridge.cs、MonologueController.GameCommentary.cs、GameCommentaryPolicy.cs |
実装済み(ゲーム別 AI は対象外) |
確認の結果と残課題は 05_known_issues.md の「2026-10-13 エンタメモード」。
使い始めの手順
- Admin → キャラ → すべての詳細設定 → 応答スタイル →「🎭 エンタメモード」で「エンタメモードを使う」を ON にし、笑いの型・欠点・持ちネタ・会話例を書く(ここが面白さの大半を決める)。
- 相方を出すなら「相方」グループで表示名・性格・役割(ツッコミ等)・声(VOICEVOX 話者 ID)を入れる。
- 企画をやるなら台本エディタのノードで「🎬 企画(状況が進む物語)」に状況・出来事・投票を書く。
- チャンネルポイントを使うなら、配信アプリで Twitch の認証をやり直してから「報酬名=アクション」を書く。
- 配信アプリは更新版(このコミット以降でビルドしたもの)が必要。