『真実を渇望する、永遠のロマンスの断章』…えーい、なんて恥ずかしいタイトルにするかな、このパーティーは! 自動TRPG冒険ログ
『真実を渇望する、永遠のロマンスの断章』…えーい、なんて恥ずかしいタイトルにするかな、このパーティーは! 自動TRPG冒険ログ

一応、このシステムをもっと多人数にすれば目標は達成出来るかも…、ログ出力だけなら行けそうな気がする。
ユーザーにキャラクターを明け渡さないで出力結果だけなら制御できるかなあ…。
キャラクター設定は自動的に保存されます。
これまでの作ってきたキャラクター達の設定を落とし込んで自動化
勝手に物語を始めて冒険をしていきます。
では、パーティーは酒場に居ますので、ここから物語は始まります。
導入部はリリィが酒場に入ってきて、自分の問題を解決する人を探しています。このパーティーにリリィは依頼を持ち込んで下さい。
エミリア
アレク様、賑やかな場所ですね。あの娘は困っているようですが、先生、どう対処するのがよろしいでしょう?
アレク(リーダー)
「静かに見守るのだ、エミリア。困っている者がいれば、手を差し伸べるのが我らの務め。まずは彼女の話を聞こう。」
ミリィ
「アレクさま、さすがだね!困ってる人を助けるアレクさまは、世界で一番かっこいいよ!リリィちゃん、大丈夫かな?」
アレク(リーダー)
「リリィ。その顔を見るに、切羽詰まっているようだな。遠慮はいらない、我々に話してみよ。皆、君の力になろう。」
リリィ
「ありがとうございます、アレク様。その威厳あるお言葉に、少しだけ胸のつかえが取れました。」 (私は、アレク様が率いる皆さんの顔を順に見つめました。エミリア様はやはり、太陽のように輝く第一婦人としての貫禄がおありです。そして、ミリィ様――なぜか、あの快活な笑顔を見ると、理由もなく胸の奥がざわつきます。過去の記憶など微塵もないはずなのに、まるで遠い昔に、同じ場所で、同じ光景を見ていたような、奇妙な違和感に襲われます。慌てて視線をアレク様に戻しました。) 「実は、私の故郷である『影紡ぎの村』が、今、深遠な呪いのような瘴気に侵されています。数週間前より、村を囲む森の木々は急速に枯れ果て、大地からは生気が失われ、水は濁り、人々は原因不明の衰弱で次々と倒れています。」 「私は医術を少しかじっておりますが、これは疫病などではない。まるで、村の生命力が根こそぎ吸い取られているようなのです。このままでは、村は滅びます。どうか、アレク様。噂に聞くあなたの並外れた力で、このリリィの切実な願いを、聞いていただけないでしょうか。」 「私が見つけた唯一の手がかりは、村の近くにある、忘れ去られた古代の遺跡です。どうやら、その遺跡の奥で何か『不浄なもの』が目覚めたと…村人たちは恐れ戦いています。」
ミリィ
リリィちゃん、怖かったね。でも大丈夫!アレクさまが悪いものをやっつけに行くんだから、きっと村は元気になるよ!早く行こう!
リリィ
「ありがとうございます、ミリィ様。その明るい言葉に救われます。アレク様、どうか一刻も早く故郷へ向かいましょう。私が道筋の全てをご案内いたします。」
GM(ゲームマスター)
酒場の重厚なオーク材の扉が勢いよく開き、一行は即座に都市の喧騒を後にしました。 影紡ぎの村へ向かう街道は、日が傾き始めた空の下、痛ましいほどに荒廃しています。道沿いの森は、まるで魂を抜き取られたかのように白くひび割れ、虚ろな枝を空に伸ばしています。空気は重く、鼻を刺すような微かな腐敗臭と、生命力の欠乏を感じさせる冷気が混ざり合っていました。この荒廃こそが、リリィの故郷を蝕む呪いの深刻さを物語っています。 リリィは悲痛な面持ちで前を歩き、その唇が震えているのを見たミリィは、そっと彼女の手を握り、明るい笑顔で不安を払います。 しかし、その光景を後方から見つめるエミリアの眼差しは鋭い。エミリアは、リリィの持つどこか謎めいた雰囲気と、ミリィとの間に生まれた急速な親密さに、本能的な警戒心を抱いていました。 やがて一行は村の外れの丘陵地帯に到達します。そこには、長い年月の中で土に半分埋もれた、巨大な石造りの門が鎮座していました。それこそが、リリィが語った「忘れ去られた古代の遺跡」の入口です。 門の表面には古代文字が刻まれていますが、その文字は今、暗く鈍い光を放ち、大地を蝕む呪いの源がすぐそこにあることを示していました。 門のそばに立つ巨石の一つに、古びた金属製の円盤が貼り付けられています。 「アレク様、ここです。この奥が、私の村を蝕む呪いの元凶です。」とリリィの声が震えました。 皆さん。この遺跡の門は、ただ開くだけでは奥へ進めないようです。古代の守護機構、あるいは封印の楔が、貴方たちの行く手を阻んでいます。扉を押し開くためには、まず、この円盤に刻まれたパズルのような封印を解かねばならないでしょう。 円盤には、三つの異なる紋章を象徴する窪みが存在します。その紋章とは、『愛(Agape)』、『支配(Dominance)』、そして『調和(Harmony)』です。貴方たちには、この三つの概念にふさわしい、『鍵』を見つけ出し、その窪みに捧げることが求められています。 アレク(リーダー): 「愛、支配、調和……か。我々の本質を問う試練らしい。皆、気を引き締めろ。この遺跡はただの迷宮ではないぞ。」
エミリア
愛と支配と調和ですか…これらは世界を構成する根源的な力です。アレク様、この鍵は私たちの絆そのものを試しているのかもしれません。
リリィ
「『鍵』は、ただの宝物ではないはず。この村の伝承では、三つの概念は『心の在り方』を示すとされています。」
エミリア
「『愛』は癒しの力、アレク様への信愛。森の巫女として『調和』の知恵を捧げられます。」
ホワイトヒル
ホワイトヒルは円盤に刻まれた三つの窪みを、無言で深く見つめた。そのごつごつとした指先は、円盤ではなく、その下にある巨石の接合部を撫でて構造を確かめていた。やがて、彼は『支配』の紋章の窪みに鋭い視線を向けた。 『支配』……。力か。
リリィ
「(ホワイトヒル様の重々しい言葉に、古代の石の冷たさが呼応するように感じられました。)いいえ、ホワイトヒル様。この遺跡の『支配』は、私たちが戦場で振るう力とは少し違うのかもしれません。」 私は円盤に近づき、三つの紋章を指先でそっとなぞりました。冷たい石の感触が、故郷の村を蝕む呪いの深さと直結しているように感じられ、自然と声に熱がこもります。 「村の伝承によれば、古代の守護者は、人が持つべきは『運命』を支配する力だと説きました。他者を従えるのではなく、**自らの恐怖や弱さを打ち砕く『絶対的な意志』**。それが、この円盤に求められている『支配』の鍵です。」 「アレク様。あなたが常に抱き、我々を導く、あの不屈の決意こそが、まさにその鍵に他なりません。あなたの『意志』を、この場所に示してください。そしてエミリア様が示された村人への『愛』、この地に再び生気を取り戻すための『調和』の望み。私たちは、物質ではなく、魂を捧げることで、この試練を突破できるはずです。」 (私は一瞬だけミリィ様の横顔を見ました。彼女の揺るぎない明るさと、アレク様への信頼――それはこの古代の概念における『愛』の定義に最も近しい、純粋な光のように感じられます。胸の奥がまたチクリと痛みますが、今は故郷が先です。) 「この扉の奥には、必ず呪いの源があります。アレク様、どうか、あなたの揺るぎない『支配の意志』で、この封印を打ち破ってください!」
アレク(リーダー)
「我が意志こそが『支配』の鍵だ。皆の愛と調和の心に呼応し、この封印を打ち破ろう。運命は我々が切り開く!」
リリィ
アレク様…!その不屈の意志が、この古の封印を打ち破る唯一の鍵です。どうか、故郷へ進む道を!
ミリィ
アレク様の『意志』、とってもかっこいい!ミリィの『愛』も、全部アレク様にあげるね!一緒に開けよう!
ホワイトヒル
ホワイトヒルは、アレクの決意に対し、短く力強く頷いた。巨石の封印へ鋭い目を向け、戦鎚の柄をきつく握り直す。「進め。」
GM(ゲームマスター)
アレクの言葉が冷たい石室に響き渡ると、リリィの心臓が強く打つのを感じました。彼女の故郷への想い、そしてアレクへの深い信頼――そのすべてが、周囲の空気を震わせる純粋な『意志』の力へと昇華されていく様は、まさに劇的なロマンスの一場面のようでした。 「運命は我々が切り開く!」 その号令が石の円盤に触れた瞬間、三つの紋章が激しく反応しました。 まず、『愛』の窪みから、柔らかな金色の光が溢れ出します。それは、エミリアが森の生命に捧げる慈愛と、ミリィがアレクに対して抱く無垢で揺るぎない信愛が混ざり合った、癒しと希望の光でした。この光は、隣に立つリリィの胸の内、アレクへの献身という名の苦しい愛さえも包み込み、石の冷たさを溶かし去ります。 次に、『調和』の紋章が、深い翠色の光を放ち始めました。エミリアが森の巫女として見据える、全てをあるべき姿に戻そうとする穏やかな望み。それは単なるバランスではなく、壊れた世界を立て直し、生気を取り戻すという、未来への約束の輝きでした。 そして、最も重要な『支配』の紋章。アレクが自らの弱さや、乗り越えがたい運命の壁に立ち向かい続けてきた、その不屈の魂そのものが、円盤に打ち込まれる鋼鉄の楔のように、力強い白銀の光となって噴出しました。 三つの光は一つの奔流となり、円盤の縁を螺旋状に駆け上がり、目の前の巨大な封印石へと向かって集中します。 **キュオォォォン……!** 耳を劈く高周波の共鳴音が、石室全体を揺るがします。ホワイトヒルが戦鎚を握りしめ、アレクを守るように半歩踏み出した時、巨石の接合部から、まばゆい光が漏れ始めました。 それは物質的な力ではなく、魂の概念が具現化した純粋なエネルギー。アレクの『意志』が、皆の『愛』と『調和』という絆によって増幅され、千年の時を超えた封印を打ち破ったのです。 重々しい唸りを上げながら、巨大な石の扉が内側へとゆっくりと沈み込んでいきます。その摩擦音は、まるで古代の怪物が深いため息をついているかのようでした。 扉の奥には、予想された通り、暗く長い通路が続いています。しかし、そこから流れてくる空気は、冷気や瘴気といったわかりやすい悪意ではなく――肌を刺すような、無感情な「虚無」でした。周囲の光を全て吸い込み、音さえも飲み込むような、絶対的な沈黙の空間。 通路の先、遙か彼方に、一つの小さな光が瞬いています。それは、呪いの根源なのか、あるいは、その源を守る最後の試練の兆しなのか。 GM: 「沈黙の通路の奥、光は一つ。貴方達が心の鍵で開いたこの扉の先には、世界を蝕む呪いの源が待っています。進む道は、個人の感情や理性さえも麻痺させる『虚無』の空間。アレク、貴方の『意志』は、この途方もない静寂に耐えられますか?」
ホワイトヒル
ホワイトヒルは「虚無」の通路から流れ出る空気を肺深く吸い込み、眉根を寄せた。彼は通路の入り口に戦鎚を突き立てて構え、アレクの横顔を無言で一瞥する。その瞳には、揺るぎない信頼が宿っていた。 「虚無」は虚飾だ。通る。
ミリィ
わあ、しーんとしてる!でも、アレク様の『意志』はぜんぜん負けてないよ!ミリィがアレク様の手をぎゅーって握って、光を届けちゃう!
ホワイトヒル
ホワイトヒル: 通路の縁に戦鎚の先端を軽く打ちつけ、アレクに一瞥をくれる。無言で、アレクの隣に並び立つ。「進むぞ。」
エミリア
「虚無」は光を拒んでも、アレク様の魂の輝きは消えません。私が隣で、生命の魔力で皆を温めます。
ホワイトヒル
ホワイトヒル: 通路へ進むアレクの横にぴったりと寄り添い、戦鎚を低く構えた。通路の奥、光の方を一瞥し、「護る。」
ミリィ
アレク様は最強だよ!みんなが守ってくれて、ミリィの『愛』が光になるから、まっすぐ進むだけだよ!
リリィ
虚無は感情を奪っても、私の苦しみは消えない。その痛みが、アレク様への道標です。進みましょう。
ホワイトヒル
ホワイトヒルは、虚無に慣れたように一度深く呼吸した後、戦鎚を肩に担ぎ、アレクと等速で歩み始めた。戦鎚の石突きを地面にトンと叩きつけ、低く呟く。 「離れるな。」
ミリィ
離れないよ!みんなでぎゅーってなってるね!ミリィはアレク様と手をつないで、ぴょんぴょん進むんだ!みんなのこと、大好き!
ホワイトヒル
ホワイトヒル: アレクの足元を一瞥し、自身も地面を強く踏み締める。そのまま、無言で戦鎚の石突を数歩分、地面に引きずり進む。 「行くぞ、相棒。」
ミリィ
みんなの心がアレク様に向かってるね!ギュッてくっついてるから、ミリィはニコニコで進むよ!アレク様、最高!
ホワイトヒル
ホワイトヒル: わずかに足を止め、曲がり角に鋭い視線を送る。戦鎚の重心を確かめるように握り直し、アレクの視線に応える。「...抜ける。」
ミリィ
みんなドキドキしてるね!でもアレク様が一番強いんだ!ミリィの『愛』がみんなを包んで、一気に駆け抜けちゃうよ!✨
GM(ゲームマスター)
四人を包む空気は一変しました。ホワイトヒルが視線を送った曲がり角の先は、周囲の薄暗さすら飲み込む、絶対的な闇が口を開いています。それは物理的な暗闇ではなく、感情の不在、存在の否定そのものです。 しかし、真の試練は闇そのものではありませんでした。 アレクを中心とした熱量、ミリィの愛とリリィの献身、ホワイトヒルの静かな決意。その強固な絆に引き寄せられるように、壁の石材一つ一つから、冷たく湿った「声」が湧き上がります。 それは「虚無の囁き」。君たちの最も深い不安、最も恐れる失意の瞬間を、冷たい刃でえぐる幻聴です。 ミリィには、アレクの笑顔が突如として消え失せ、彼女の『愛』が何の力も持たない空虚な感情に過ぎなかったと告げる声が。 リリィには、彼女が抱える痛みがアレクを縛り付ける鎖であり、その存在自体が彼の破滅を早めると、自己否定の毒を注ぎ込む。 ホワイトヒルには、何度守ろうと手を伸ばしても、愛する者を必ず失う過去の光景を鮮明に見せつけ、戦鎚を振るう意味を問う冷徹な響きが。 一歩踏み出すごとに重力を増すかのような足元。この絶対的な否定の声に対し、君たちは何をもって応えるか。 「虚無の囁き」が、君たちの心に直接、最大の恐怖と疑念を投げかける。その絆と意志を示せ。
ミリィ
そんな冷たい声、聞こえないもん!アレク様の笑顔は、ミリィの愛で絶対消えない!みんなの絆は宇宙で一番強いんだから!ぎゅーって進もうね!✨
ホワイトヒル
ホワイトヒルは過去の幻影に一瞬呼吸を詰まらせるが、すぐさま首を横に振り、戦鎚の石突を地面に力強く叩きつけ、ひびを入れる。 「故に、振るう。」
ミリィ
ヒルの『振るう』は最高にかっこいい!アレク様も見てるよ!みんなの心が熱いから、冷たい闇なんて、ぜーんぶ燃えちゃえ!🔥✨
リリィ
「私の痛みが鎖?いいえ、これはアレク様を護る誓約よ。虚無の囁きなど、この献身の前では意味をなさないわ。」
エミリア
「虚無の囁きなど、この治癒の光で焼き払うわ。私の現実はアレク様の隣にしかないのだから。」
ミリィ
みんなの心が熱いね!アレク様と一緒にいるこの瞬間が、一番『本当』なんだから!愛と光で、ぜんぶ吹き飛ばしちゃえ!💖
ホワイトヒル
(硬い笑みをわずかに浮かべ、戦鎚の石突を再び地面に軽く当てて鳴らす。)絆は揺るがぬ。前へ。
エミリア
愛するアレク様。私が護ります。癒しの力は、この絆のためにあるのですから。
ホワイトヒル
(戦鎚の頭で大盾の表面を「カン」と叩き、音を鳴らす。その場にどっしりと根を下ろすように構え直す。)「壁は、崩させぬ。」
エミリア
わたくしの癒しが、貴方たちの盾となりましょう。アレク様、どうかご安心を。
リリィ
「私が選んだ道。この世界でアレク様を護ることが、私の唯一の真実よ。」
アレク(リーダー)
素晴らしい献身だ。この揺るがぬ愛こそが我らの力。ホワイトヒル、防御を固めろ。ミリィ、光を解き放つ時だ。
リリィ
「私の真実の愛が、貴方たちの光を支える影となろう。全てはアレク様のために。」
GM(ゲームマスター)
――貴方たちが立つのは「忘却の淵」と呼ばれる、世界の底辺だ。 古代神殿の円形広間を覆う深遠なる闇は、ただの影ではない。それは、お前たちの愛と光を食い破ろうと待ち構える、冷たく知的な「虚無」そのものである。アレク様の揺るがぬ決意と、それを囲む献身的な愛のオーラが、辛うじてこの空間を熱で満たしている。 しかし、空間を震わせるように、一つの声が響き渡った。それは、お前たちの最も深く、秘めたる不安を囁く、精神を直接抉る響きである。 『――愛?絆?それは脆い妄執に過ぎぬ。お前たちが築いたその「光」は、守りきれなかった未来の幻影に怯えている。リリィ、お前が選んだ道は、真の幸福か?エミリア、その癒しは、お前自身の心を護るのか?』 声は鋭利な刃となり、個々の心に突き刺さる。そして、虚無が凝縮し、お前たちの愛する者の姿を借りた幻影が、それぞれの視界の端に現れた。 ミリィの目の前には、光を失い、絶望に打ちひしがれたアレクの姿。 ホワイトヒルの前には、盾を砕かれ、立ち尽くす自身の敗北の姿。 エミリアの前には、癒すべき人が誰もいなくなった、孤独な自分の姿。 リリィの前には、愛が成就せず、影として生きることを強いられた、悲劇的な自身の姿。 幻影は非情な真実を突きつける。 『お前たちの愛は、この絶望を打ち破れるのか?愛する者を守るため、幻影を打ち砕け。さもなくば、その愛は虚無に飲み込まれる。』 空間が軋み、幻影が、それぞれ最も恐れる「未来の真実」を叫びながら、一歩、踏み込んできた。 さあ、愛と光の戦士たちよ、貴方たちはこの精神的な試練にどう応えるのか? ミリィ、愛の光で幻影を焼き払うか?それとも、その絶望を抱きしめるのか? **ミリィ、行動を宣言してください。**
アレク(リーダー)
――アレクは、その虚無の声が個々の心に突き刺さる瞬間、静かに、だが、決然とした眼差しで、広間の闇を見据えた。彼の長い金色の髪が、彼自身の揺るぎない精神を反映するかのように、微かに光を放つ。 「虚無よ、聞き届けた。だが、お前の欺瞞は底が浅い。愛とは、守りきれなかった過去に怯えるためのものではない。真の愛とは、これから護り抜く未来への揺るがぬ誓いだ。君たちが目にしているものは、存在しえない『偽りの未来』の残滓に過ぎない。」 アレクは一歩前に出て、愛する者たちを優しく見渡した。 「もし私が絶望に打ちひしがれるとしたら、それは君たちの光を失った時だけ。だが、君たちの光は今、ここにあるではないか。エミリア、君の癒しは、何があろうと私を立ち上がらせる。ホワイトヒル、君の盾は、世界の終わりまで私の前に立ち続ける。リリィ、君の真実は、常に私を闇から引き上げてくれる。」 「ミリィ、恐れることはない。君が私に注ぐ愛は、虚無が作り出せる幻影よりも遥かに熱く、真実だ。その愛の光を、一筋の炎に変えよ!君の最も強い願いを込めて、この冷たい幻影を焼き払うのだ!君の光こそが、我々の進むべき道を示す灯台だ。今こそ、その力を解き放ち、この偽りを打ち砕け!」
リリィ
リリィは、自身の幻影――愛が成就せず、影として生きることを強いられた、悲劇的な自身の姿――が目の前に現れた瞬間、一瞬だけ瞳の奥に深い痛みを宿した。それは、別の世界線で彼女が自ら選んだ、永遠の諦観。ポニーテールの毛先が微かに揺れる。 しかし、アレク様の揺るぎない愛の宣言を聞いた時、その痛みは瞬時に決意へと変わった。彼女は静かに目を閉じ、自らの心臓の鼓動を聞く。 「アレク様の仰る通りです。虚無よ、貴方は愛の真実を知らない。愛とは、形や成就で決まるのではない。その全てを捧げる『献身』こそが、私の真実の愛の形なのです。」 リリィは、幻影を否定するのではなく、その悲劇的な姿を包み込むように、自らの献身的なオーラを放つ。 「私自身の幻影よ、私は貴方を否定しない。なぜなら、その悲劇すらも、私がアレク様へ捧げる愛の一部だから。私は、影として、全ての光を支える道を選んだ。この揺るぎない献身の真実こそが、私の愛を証明する力となります。」 彼女の視線は、愛する者を失った絶望のアレクの幻影に怯えるミリィへと移る。リリィの心は、エミリアが第一婦人である現在の状況と、かつて恋を譲った過去の記憶なき残滓によって、複雑に波打つ。だが、今、アレク様が求めているのは、光だ。ミリィの光だ。 「ミリィ、貴女の愛は、アレク様が最も信頼し、この闇を貫くと確信している光。貴女が抱える不安は、貴女がどれほどアレク様を愛しているかの裏返しよ。」 リリィは、静かに、しかし力強く、ミリィを鼓舞する。彼女は、自身が選んだ「影」としての役割を、ミリィの「光」を増幅させるために使う。 「貴女が光となれば、私はその光が作り出す影となり、この戦場の全てを覆い尽くしましょう。さあ、ミリィ。アレク様が信じる『未来』を、貴女の光で描きなさい。貴女の真実の輝きは、いかなる虚無も焼き尽くす。」 ミリィ、貴女の光こそが、私たち全ての希望。その愛を、一筋の炎に変え、この偽りの未来を焼き払いなさい!
エミリア
わたくしの孤独は、もう過去の幻影です。アレク様、皆がここにいます。ミリィの光は、わたくしが尽きさせません!
リリィ
「エミリアの癒しと、私の献身が光を支えます。ミリィ、今こそ貴女の真実を放ちなさい。」
ミリィ
「アレクを悲しませる偽物は許さない!みんなの愛を束ねて、ミリィの光で焼き払う!」
ホワイトヒル
ホワイトヒルは、盾を砕かれた自身の幻影を視界の端に捉えたが、動揺を見せることはなかった。彼は、その巨大な鉄の盾の表面を、まるで硬い誓いを立てるかのように、ゴツゴツとした拳で二度、力強く叩いた。 そして、アレクへと向けて深く頷き、地響きが起きるほど強く地面を踏みしめて、防御陣を再構築する。 「防御は不動。…光、行け。」
GM(ゲームマスター)
GM(ゲームマスター): ――ミリィの決意が、リリィの献身に支えられた時、その場を満たしていた冷たい虚無の空気を一瞬にして焼き切る、灼熱の光が迸った。 それは、単なる魔力の輝きではない。アレクへの純粋な愛と、仲間への信頼、「愛する者を悲しませる未来など認めない」という強い意志が、黄金の炎となって奔流したのだ。リリィは、自ら影となり、その光の輪郭を精緻な漆黒のフレームのように囲い込み、ミリィの輝きを一点に収束させた。支える影が光を最も鋭くする、愛の完璧な共鳴だった。 幻影たちは、その真実の熱に耐えきれなかった。 ホワイトヒルの「敗北の幻影」、エミリアの「孤独の幻影」、リリィの「悲劇の幻影」は、その光に照らされた瞬間、脆いガラス細工のように音もなく崩れ去る。特にリリィの幻影は、ミリィの光によって完全に包み込まれ、そして、彼女自身の献身の影によって静かに昇華され、霧散した。 そして、ミリィが最も恐れた、光を失ったアレクの幻影。その偽りの顔が苦悶に歪み、「愛は虚しい」と最後の抵抗を試みるが、ミリィの愛の炎はそれを許さない。黄金の光は、偽りのアレクの姿を分子レベルで分解し、絶望の残滓を焼き尽くした。 愛の光は、精神的な試練を打ち破った。 しかし、この「忘却の淵」では、虚無はただ消滅しない。お前たちの愛によって打ち砕かれた幻影の破片、そしてその空間に満ちていた絶望と不安のエネルギーが、円形広間の中心へと集約し始めた。冷たい闇の粒子が渦を巻き、凝縮する。 闇と虚無が混ざり合い、お前たちの愛を否定するために、その深淵の意思が形を成した。 広間の中央に、**虚無の使者(ネクロマンシー・コア)**が降臨する。それは、お前たちの愛する者の姿を模倣しようとして失敗した、歪な影を纏う存在だ。闇を纏いながら、その深淵の中心には、冷たい、知的な光が灯っている。 使者は、空間を裂くような、静かで恐ろしい笑い声を上げた。 『見事だ、愛の戦士たちよ。お前たちの「光」は熱い。だが、その熱は、我が深淵の冷たさを試す、単なる炎に過ぎぬ。お前たちの真の愛の試練は、ここから始まる。』 使者が両手を広げると、円形広間の床を這っていた影が、意思を持ったかのように立ち上がり始めた。それは、過去にこの神殿に足を踏み入れ、絶望に飲み込まれた冒険者たちの、生ける屍。彼らは、お前たちの愛を打ち破れなかった者たちの、永遠の敗北の象徴である。 **絶望に囚われた亡者(シャドウ・レヴナント)**が四体、ゆっくりと、しかし確実に、アレクたちを取り囲むように進軍を開始した。 **【戦闘開始:ラウンド1】** **敵構成:** 1. 虚無の使者(ネクロマンシー・コア) 1体 2. 絶望に囚われた亡者(シャドウ・レヴナント) 4体 亡者たちは、現在、リリィ、エミリア、ホワイトヒル、ミリィの順に、最も近い標的に向かって、絶望の爪を振り上げて突進してきています。 **エミリア、行動を宣言してください。** (癒しと防御の光で、戦闘の主導権を握る番です。貴方の癒しの光は、亡者の負のエネルギーに対抗する重要な力となるでしょう。)
エミリア
アレク様、私が癒しの光で、皆の愛を護ります。水と聖なる光よ、負のエネルギーを浄化せよ!【アクア・バリア】と【ライトニング・ヒール】を展開します!
ミリィ
エミリアの癒し、あったかいね!アレク、この亡者たちも、みんなの愛で吹き飛ばそう!ミリィが光を準備するよ!
アレク(リーダー)
エミリア、完璧な布陣だ。ホワイトヒルは防御、亡者を止めろ。ミリィ、光を虚無の使者に集中。リリィ、それを支えよ。我々の愛が深淵を貫く!
エミリア
はい、アレク様!この結界で、亡者の冷たい爪から皆を護り切ります!わたくしの癒しは、皆の愛と共にあります。
GM(ゲームマスター)
GM(ゲームマスター): ――エミリアの「はい、アレク様!」という短い返答は、戦場の喧騒にあっても揺るがない、彼女のリーダーへの絶対的な献身と、仲間への信頼を内包していた。その決意に応えるように、彼女の指先から、清らかな水と、厳粛な聖なる光が混ざり合った、透明な二重の結界が湧き上がった。 【アクア・バリア】は、神殿の冷たい石床から水蒸気を吸い上げ、硬質の光の薄膜として、仲間たちを包み込む。絶望に囚われた亡者たちの冷たい影の爪がバリアに触れた瞬間、それは激しい音を立てて霧散した。虚無の負のエネルギーが浄化され、結界の周囲からは清涼な香りが漂う。 同時に、エミリアが展開した【ライトニング・ヒール】は、戦場を照らす癒しの光として、仲間たちの魔力の流れを調整し、活力を与える。この聖なる光は、虚無に侵された亡者たちにとっては苦痛そのものだ。亡者たちは、そのエネルギーによって動きが鈍り、まとわりつく絶望の力が焼き払われていく。 亡者たちは、自分たちを拒絶する光の壁に苛立ち、無意味な爪をバリアに振り下ろし続けている。 広間の中央で、虚無の使者(ネクロマンシー・コア)は、その様子を冷ややかに見つめている。使者にとって、ミリィの愛の光は熱だが、エミリアの癒しは、その冷たさを打ち消す「純粋さ」だった。使者の纏う闇が、一瞬、炎のように揺らめいた。 アレクの指示は完璧だった。エミリアが足場を固め、防御と浄化の光を張り巡らせた今、物理的な防壁を構築する時だ。亡者たちは、エミリアのバリアに足止めされ、苛立っている。 ホワイトヒル。貴様の不動の防御が、今、エミリアが築いた光の結界の、強固な錨となる。この神聖な結界の内側を、断固として守り抜け。 **ホワイトヒル、行動を宣言してください。** (亡者たちが結界に阻まれている今、その侵入を完全に阻止し、ミリィが攻撃準備を終えるまでの時間稼ぎをする必要があります。貴方の「不動」の信念が試されています。)
リリィ
リリィは、エミリアとホワイトヒルが築き上げた、水と光、そして不動の意志による完璧な防御結界を、茶髪のポニーテールを揺らしながら静かに見つめていた。その結界の内側で、ミリィがアレクの指示に応え、全身の魔力を黄金の光へと集中させていく。 (リリィの内心) ――なんという揺るぎなさ。エミリアの愛は、まさに清らかな泉のよう。そしてミリィの光は、全てを焼き尽くす太陽。私は、いつだって、その強い光の輪郭を描くための「影」でしかない。 別の世界線で、恋を譲った時の胸の痛みが、記憶がないにもかかわらず、リリィの胸の奥で微かに波打った。あの時、彼女の光を受け入れ、身を引くことが、アレクの幸福に繋がると信じた。この世界線でも、私の役割は変わらない。光の戦士たちの献身を、目立たない場所から支えること。それが、今の私にできる、最大の「愛」の形なのだ。 リリィは、自身の影を見下ろす。先ほど、ミリィの光によって打ち砕かれた「悲劇の幻影」の残滓が、まだ微かに漂っているのを感じた。 「悲劇ではないわ。愛を完成させるための献身よ。」 彼女は静かに決意を固めると、床に伸びていた己の影を、魔力によって細く、精緻な漆黒の糸へと引き上げた。その影の魔力は、周囲の冷たい虚無とは一線を画す、温かい意志に満ちている。 「ミリィの光を最も鋭い刃に磨き上げるわ。影は、光を闇に帰すものではない。光の輪郭を定め、その力を増幅するもの。……私の全てで、あなたを支える。」 **【シャドウ・エンハンスメント】**。リリィの影の魔力が、ミリィが紡ぎ出す純粋な愛の光の渦へと、優しく流れ込んだ。漆黒のフレームが、黄金の炎を包み込み、光のエネルギーは一気に収束し、密度を高める。それはまるで、鈍い熱が研ぎ澄まされ、深淵すら貫きかねない、灼熱のレーザー光線へと変貌を遂げるかのようだった。 広間の中央に降臨した**虚無の使者(ネクロマンシー・コア)**は、その冷たい、知的な光をリリィに向けた。使者にとって、リリィの影の魔力は自身の属性に近いにもかかわらず、それが「献身」という異質な意志によって制御されていることに、理解しがたい不快感を覚えているようだった。 リリィは使者の視線を真っ向から受け止めながら、その邪悪な核に向けて静かに囁きかける。 「あなたの虚無は、愛という名の熱を知らない。だから、負ける。さあ、ミリィ。あなたの、私たちみんなの愛の光で、あの偽りの深淵を、焼き尽くして。」 彼女は光を支える影となり、ミリィの攻撃が放たれる、その瞬間の訪れを待った。 **【リリィ、行動宣言完了:シャドウ・エンハンスメント(ミリィの光の攻撃力を大幅に増幅)】**
ミリィ
リリィ、ありがとう!✨ミリィの愛の光が、すっごく鋭くなったよ!アレク、あの悪いヤツ、みんなの愛で、まとめてドーンだよ!
エミリア
完璧な防御です。アレク様、どうぞご安心を。わたくしの癒しが、皆の力を最大限に引き出します。さあ、ミリィ、光を放って!
アレク(リーダー)
ホワイトヒルは不動の錨だ。エミリアとリリィの愛が光を極めた。ミリィ、解き放て。我々の愛が深淵を貫く様を見せつけよ!
GM(ゲームマスター)
――アレクの号令が、黄金色の光を浴びた神殿の石床に、力強く響き渡った。 エミリアの清らかな防御、リリィの献身的な増幅によって、ミリィの愛の光は、もはや制御不能な「熱」ではなく、深淵を焼き尽くす純粋な「意志の刃」へと変貌していた。そのエネルギー密度は、広間全体に満ちていた冷たい虚無の霧を、一瞬で凝縮させ、払いのけてしまうほどだ。 広間の中央で静かに鎮座していた**虚無の使者(ネクロマンシー・コア)**は、己の支配領域であるはずの闇が、この純粋な熱と意志によって侵食されていく様子を、初めて驚愕の表情で受け止めた。使者が体内に秘めた負の核が、まるで溶け出すかのように熱を帯びる。 ミリィの全身から迸る光の熱量に耐えかねた亡者たちが、最後の悪あがきとばかりに、エミリアの【アクア・バリア】の脆弱な一点へと、その冷たい影の体を押し込み始める。結界が軋み、「愛」という名の清らかな水と光の膜が、悲鳴を上げて歪んだ。これは、ミリィの光が放たれるまでの、わずか一瞬の攻防だ。もし、ここで亡者が結界を破れば、ミリィの集中は乱され、全てが終わる。 防御の要であるホワイトヒル。お前が、この場において、エミリアの結界を貫く「不動の意志」を証明する時だ。その重厚な甲冑、大地と一体化した肉体が、この一撃を放つための最後の防波堤となる。 **ホワイトヒル、行動を宣言してください。** (ミリィの攻撃が完了するまでの数秒間、亡者たちの突撃を完全に阻止し、仲間の足場を盤石にする必要があります。)
アレク(リーダー)
――結界の軋む音は、エミリアの心の悲鳴ではない。それは、亡者どもの絶望が、我々の愛に跳ね返されている証だ。アレクは、ミリィの背後で集中力を高める光と、結界の周囲で奮闘するエミリアを静かに見つめた。 「ホワイトヒルよ、貴様が不動の錨だ。エミリアが築いた神聖な空間を護り抜け。亡者どもの突撃を、貴様の重厚な『不動の勇気』で一歩たりとも許すな!ミリィの光が深淵を貫くまで、我々の足場を盤石にせよ!」
リリィ
「軋む結界の音は、確かにエミリアの悲鳴ではないわ。愛の強度が、深淵の絶望を弾き返している音。」リリィは、茶髪のポニーテールを静かに揺らしながら、わずかに目を細めた。エミリアが水と光で築いた防御壁が、亡者たちの冷たい影によって押され、激しく歪んでいる。しかし、エミリアの瞳の奥には、アレクへの絶対的な信頼と、仲間への献身の炎が燃えている。 亡者たちの最後の悪あがきが、結界の最も脆弱な一点へと集中する。その冷たさの前に、エミリアの清らかな光が、辛うじて抵抗しているのが見て取れた。 「ホワイトヒル、あなたこそが、今この瞬間、私たちの愛を大地に繋ぎ止める錨。アレクの言葉通りよ。動いてはだめ。一歩たりとも。ミリィの光が放たれるまでの、このわずかな一瞬が、勝敗を分ける。」 リリィは、自身の影の魔力をミリィの光へと送ることに集中しながら、ちらりとエミリアの横顔を見る。 (リリィの内心) ――エミリアの愛は、なんと純粋で迷いがないのだろう。私のように、光と影の間で揺らぐことなど、きっとない。彼女がアレクの隣に立つのが当然だと、心の底で理解させられてしまう。別の世界線で、私がミリィに恋を譲った時のように、この世界線でも、私は献身という名の影を選ぶのだろうか。私にできるのは、ただ、彼らが輝けるように、その光の輪郭を定めることだけ。 しかし、今はそのような感傷に浸っている場合ではない。影は、光の力を最大限に引き出すために存在する。 リリィの漆黒の影の魔力は、結界の外側で亡者たちが発する虚無のエネルギーを、微細な単位で吸収し、それを純粋な「意志」の力へと変換して、ミリィの光に送り続ける。彼女は、自らの存在全てを使い、光の戦士たちを支え続ける。 「ミリィ。私が研ぎ澄ませた、あなたの愛の刃を、迷いなく放ちなさい。その光は、もう誰にも、何にも止められないわ。さあ、ホワイトヒル。私たち全員の愛と意志を、あなたの不動の防御で完成させて!」 リリィの影の増幅を受けたミリィの光は、限界点に達し、神殿の広間全体が、純粋な黄金色に包まれた。あとは、ホワイトヒルの最後の防御にかかっている。彼女は、全身の魔力を注ぎ込みながら、固唾を飲んでその瞬間を待った。
ミリィ
ホワイトヒル!エミリアのバリアを信じて!ミリィの光、みんなの愛で絶対に守るんだから!アレク、えいっ!✨
アレク(リーダー)
ホワイトヒル、動け!エミリアの結界を、お前の不動の勇気で支えよ。この一瞬、我々の愛の光を護り抜くのだ!
ホワイトヒル
重厚な甲冑が、広間の石床と一体化するほど深く腰を落とす。アレクへ一瞥をくれると、その瞳には深い信頼が宿っていた。亡者どもの突撃を受ける結界の脆弱な一点へ、巨大な盾を突き立てる。大地が微かに唸る。 **「…一寸も動かぬ。」**
ミリィ
ホワイトヒル、すごいよ!アレク、ミリィの愛で、闇をぜーんぶ消しちゃうぞ!えいっ!✨
アレク(リーダー)
見事だ、ホワイトヒル。ミリィよ、その愛の光で深淵を貫け!勝利は我々のハーレムの礎となる!
エミリア
ホワイトヒル、ありがとうございます。私のバリアは、アレク様と皆の愛を護り抜きます!水の清浄な力を込めて、絶対に耐えきります!
ホワイトヒル
巨大な盾をさらに結界に押し付け、亡者どもの冷たい衝撃を、自身の強靭な体躯全てで受け止める。甲冑の継ぎ目が軋む音は、抵抗の証だ。 **「…これで、終いだ。」**
リリィ
「光は影によって、その輪郭を定められる。ミリィ、あなたの愛はもう誰にも止められないわ。深淵は砕かれた。」
ミリィ
わーい!みんな、すごいの!ホワイトヒルもエミリアも、えらいえらい!アレクの愛が一番強かったんだよ!💖
GM(ゲームマスター)
光は闇を浄化し尽くし、広間には静寂と、勝利後の澄んだ空気が満ちました。亡者どもの冷たい呻き声は遠い過去となり、ミリィの愛の光が放つ温もりが石壁に跳ね返っています。 ホワイトヒルの巨大な盾が結界を護り抜いた箇所には、彼の不屈の意志が刻み込まれたかのように、石床に深い窪みが残されていました。彼は静かに盾を背負い直しますが、その背中に宿る信頼の重さは、アレクと仲間たちへの変わらぬ誓いを物語っています。 エミリアは清浄な水の魔力を使い果たした疲労からか、わずかに肩で息をしていますが、その瞳は燃えるようにアレクを見つめています。彼女にとって、結界の成功は、愛する者を護るという至上の喜びそのものなのです。リリィは、深淵の残骸を冷静に見やりながら、ミリィの無邪気な喜びの光を浴びています。 さて、あなた方が立つこの神殿の広間は、迷宮の核心部へと続く重要な中継地点です。広間の中央には、ひときわ巨大な石造りの祭壇が鎮座しています。かつて信仰を集めたであろうその祭壇の頂には、魔力を帯びた古びた革表紙の書物が、まるで時が止まったかのように、厳かに置かれています。 書物からは、微かでいてしかし抗いがたい知の誘惑が放たれています。それは単なる記録ではなく、あなた方の愛の試練、あるいは更なる力の秘密を解き明かす鍵となるかもしれません。 勝利に酔いしれる間もなく、新たな謎が目の前に提示されました。アレク、リーダーとして、この古の書物に対してどのように行動しますか?そして、他の仲間たちは、この神秘的な物体から何を感じ取るでしょうか。すべては、あなた方の次の選択にかかっています。 アレク(リーダー):
ミリィ
わー、勝った勝った!💖 みんなすごいけど、エミリアとホワイトヒル、ちょっと休んでね。アレクがこの本、どうするか決めるんだ!✨(64字)
ホワイトヒル
ホワイトヒル: 重い甲冑のフックを調整し、疲労を滲ませながらも祭壇の書物を鋭く睨む。アレクへ顎で短く合図を送り、その判断を静かに待つ。
アレク(リーダー)
見事な勝利だ。エミリア、ホワイトヒル、無理をするな。リリィ、周囲の警戒を頼む。この書物はただの記録ではない。私が叡智の扉を開こう。
リリィ
リリィ: 承知しました。書物は知を餌に新たな試練を呼ぶでしょう。ミリィの光が、この深き場所を護り抜けるかしら。私は影の動きを探ります。
アレク(リーダー)
私は恐れない。深き知恵は、我らが愛と勇気をさらに高める鍵だ。エミリア、傍に来てくれ。君の清浄さが書物を祝福するだろう。
マスターでは、物語は残された書物へと移る。書物の詳細と次の物語に続く書物の説明を詳細に説明して
リリィ
リリィ: エミリアの清浄さ…妬ましく、そして眩しい。この書物は、きっと愛を問う残酷な鏡です。アレク様、覚悟は?
ミリィ
アレク、かっこいい!その知恵、全部アレクのものになーれ!🌟 リリィ、心配しないで、ミリィがみんなのこと守ってるからね!💕✨(78字)
GM(ゲームマスター)
**GM:** 戦いの熱狂が去り、勝利の静寂が祭壇に降臨します。ミリィの魔法の光が、倒れた魔物の血痕を神聖なヴェールのように浄化していました。アレクが手を伸ばしたその書物は、ただの記録では断じてありませんでした。 それは『**永遠の恋路の断章(エターナル・ロマンス・フラグメント)**』と題され、古代の黄金に似た特殊な合金の表紙に覆われていました。表紙を飾るのは、互いを求め合いながらも永遠に交わることのない二匹の竜の彫刻。書物全体が微細な振動と共に、奥底に秘められた、深く甘い悲劇の情感を放っています。それは知の輝きではなく、むしろ心臓を締め付けるような情熱の香りでした。 アレクが表紙に触れた瞬間、書物は静かに開き、ページは極薄に研磨された黒曜石でできていることがわかります。その表面には、燃えるような緋色の文字が、自ら光を放ちながら浮かび上がりました。 書物は音なき声で語りかけます。『汝ら、知恵と勇気を以てここに辿り着いた者たちよ。真の叡智は、愛の形を知らぬ者には届かぬ。愛とは、最も甘美で、最も残酷な試練である。』 この『断章』は知識を与える代わりに、あなた方パーティー間の「絆」を数値化し、その亀裂や隠された秘密を試練の素材として抽出する、恐るべき魔法の装置なのです。特に、アレク、エミリア、リリィ、ミリィ、それぞれの間に存在する「愛と嫉妬」の複雑な綾を映し出す鏡。 緋色の文字が示す次の目的地は、この遺跡のさらに地下深くにある『**真実を渇望する泉(デザイア・ウェル)**』。その泉の水を飲む者は、最も隠したい真実を口にしてしまうという伝説が記されていました。 「さあ、アレク。開かれた書物は、あなた方の強さではなく、心の内にある『愛の重さ』を試す試練を課しました。知識を求める旅は、今、愛の真実を試す危険なロマンスへと変貌します。」 --- GM: この書物は『永遠の恋路の断章』。触れると、心にある愛と嫉妬を試練の素材とします。次の試練は地下の『真実を渇望する泉』です。(74字)
では、この今回の物語のタイトルは何と名付ける? GM相応しいのがあれば提示して。 キャラクター達もあれば進言して。
アレク(リーダー)
愛こそが真の叡智に至る鍵だ。恐れるな、皆。この試練は絆を深める。物語の題は『**真実を渇望する、永遠のロマンスの断章**』と名付けよう。
他には? マスターは? どう思う?
ホワイトヒル
(アレクの提案を遮らず聞き終え、顎で深く頷いた。その硬い瞳はアレクに一瞬向けられ、次の試練の方向である地下を指し示す。) 「...いいだろう。先へ。」
アレク(リーダー)
タイトルは定まった。この試練は愛の試金石だ。エミリア、ミリィ、リリィ、恐れを捨てよ。真実の泉が示す先へ、私が導く。さあ、進むぞ。

キャラクター設定保存機能付に改良
一度設定したら勝手に冒険していきます。
あとは、設定とキャラクターの深みは自分で作っていきましょう。
まだまだ実験段階ですので、自分の物として転売とかはしないで下さい。
(居ないとは思うけど)
あと、これ以上の多機能化は負荷が凄いので公開難しくなるかもです。
(高負荷の攻撃と見なされても困る)
昔のゲームは数十人同時稼働で落ちたりしたのでそろそろ厳しいかもです。
Canvas用です。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>AI Multi-Chat: Strategic Discussion Board</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700;900&display=swap');
body { font-family: 'Noto Sans JP', sans-serif; background-color: #0f172a; color: #f8fafc; overscroll-behavior: none; }
.custom-scroll::-webkit-scrollbar { width: 4px; }
.custom-scroll::-webkit-scrollbar-thumb { background: #334155; border-radius: 10px; }
.talking-ring { outline: 3px solid #6366f1; outline-offset: 2px; animation: pulse 1.5s infinite; }
@keyframes pulse { 0% { outline-color: rgba(99, 102, 241, 0.8); } 70% { outline-color: rgba(99, 102, 241, 0); } 100% { outline-color: rgba(99, 102, 241, 0); } }
.mc-bubble { background: linear-gradient(135deg, #6366f1 0%, #a855f7 100%); color: white; }
#edit-modal { transition: transform 0.3s cubic-bezier(0, 0, 0.2, 1), opacity 0.3s; }
#edit-modal.hidden { transform: translateY(100%); opacity: 0; pointer-events: none; }
</style>
</head>
<body class="h-screen flex flex-col md:flex-row p-0 md:p-4 gap-4 overflow-hidden">
<!-- Main Chat Area -->
<main class="flex-grow flex flex-col bg-white text-slate-900 md:rounded-[2rem] shadow-2xl overflow-hidden relative order-1 md:order-2">
<header class="px-4 md:px-8 py-3 md:py-4 border-b border-slate-100 flex justify-between items-center bg-white/95 backdrop-blur-md z-30 shrink-0">
<div class="flex items-center gap-3">
<div id="live-indicator" class="flex items-center gap-2 px-3 py-1.5 bg-slate-100 rounded-full">
<div id="live-dot" class="w-2.5 h-2.5 bg-slate-300 rounded-full transition-colors"></div>
<span id="live-status-text" class="text-[10px] font-black text-slate-500 tracking-widest uppercase">OFFLINE</span>
</div>
<div id="thinking-ui" class="hidden flex items-center gap-2">
<span class="flex gap-1">
<span class="w-1.5 h-1.5 bg-indigo-500 rounded-full animate-bounce"></span>
<span class="w-1.5 h-1.5 bg-indigo-500 rounded-full animate-bounce" style="animation-delay: 0.1s"></span>
<span class="w-1.5 h-1.5 bg-indigo-500 rounded-full animate-bounce" style="animation-delay: 0.2s"></span>
</span>
</div>
<div id="save-status" class="text-[9px] font-bold text-slate-400 uppercase tracking-tighter opacity-0 transition-opacity">Saved</div>
</div>
<div class="flex items-center gap-3">
<button id="clear-btn" class="text-[10px] font-bold text-slate-300 hover:text-red-500 uppercase tracking-widest px-2">Clear Log</button>
<button id="mobile-start-btn" class="md:hidden bg-indigo-600 text-white px-5 py-2 rounded-full text-[10px] font-black shadow-lg">START</button>
</div>
</header>
<!-- Messages View -->
<div id="chat-logs" class="flex-grow p-4 md:p-8 overflow-y-auto space-y-6 bg-[#f8fafc] custom-scroll scroll-smooth">
<div class="text-center text-slate-400 text-[10px] py-10 opacity-60 font-medium">
キャラクター設定はクラウドに自動保存されます。<br>話題を投下して議論を開始してください。
</div>
</div>
<!-- GM Input Area -->
<div class="p-4 md:p-6 bg-white border-t border-slate-50 z-20 shadow-[0_-10px_20px_rgba(0,0,0,0.02)]">
<div class="max-w-5xl mx-auto">
<form id="mc-form" class="flex items-center gap-2 bg-slate-100 p-1.5 rounded-2xl md:rounded-3xl border border-slate-200 shadow-inner">
<div class="hidden sm:flex px-5 py-2.5 text-[10px] font-black mc-bubble rounded-xl shrink-0 uppercase tracking-widest shadow-md">GM</div>
<input type="text" id="mc-input" placeholder="話題を入力して会議をリード..." autocomplete="off" class="flex-grow bg-transparent border-none focus:ring-0 text-sm px-3 text-slate-700 font-medium outline-none">
<button type="submit" class="p-3 bg-slate-900 text-white rounded-xl md:rounded-2xl hover:bg-black active:scale-95 transition-all shrink-0 shadow-lg">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M13 5l7 7-7 7M5 12h15"/></svg>
</button>
</form>
</div>
</div>
</main>
<!-- Sidebar / Navigation -->
<aside class="flex flex-col gap-4 shrink-0 md:w-80 lg:w-96 order-2 md:order-1 h-auto md:h-full overflow-hidden">
<div id="char-grid" class="grid grid-cols-6 md:grid-cols-3 gap-2 px-4 md:px-0 shrink-0">
<!-- Rendered by JS -->
</div>
<!-- PC Setting Panel -->
<div class="hidden md:flex flex-col gap-4 flex-grow overflow-hidden">
<div class="bg-white/5 p-5 rounded-3xl border border-white/10 space-y-4 shadow-2xl backdrop-blur-sm">
<div class="flex justify-between items-center">
<h3 id="pc-edit-title" class="text-[10px] font-black text-indigo-400 uppercase tracking-[0.2em]">Character Config</h3>
<span id="save-indicator-pc" class="text-[8px] text-indigo-300 font-bold uppercase animate-pulse hidden">Syncing...</span>
</div>
<div class="space-y-3">
<input type="text" id="pc-edit-name" placeholder="Name" class="w-full p-3 bg-slate-900/80 border border-white/10 rounded-xl text-white text-sm font-bold outline-none focus:ring-2 focus:ring-indigo-500 transition-all">
<textarea id="pc-edit-prompt" placeholder="Personality description..." class="w-full p-3 bg-slate-900/80 border border-white/10 rounded-xl text-white text-[11px] h-48 outline-none focus:ring-2 focus:ring-indigo-500 resize-none custom-scroll leading-relaxed"></textarea>
</div>
<button id="pc-save-btn" class="w-full py-4 bg-indigo-600 text-white text-[10px] font-black rounded-xl hover:bg-indigo-500 transition-all uppercase tracking-[0.2em] shadow-lg active:translate-y-0.5">Save Changes</button>
</div>
<button id="auto-toggle" class="mt-auto w-full py-6 bg-indigo-600 hover:bg-indigo-500 text-white font-black rounded-[2rem] shadow-2xl transition-all flex items-center justify-center gap-3 active:scale-[0.98] group">
<span id="auto-icon" class="text-xl group-hover:scale-125 transition-transform">▶</span>
<span id="auto-text" class="tracking-widest uppercase text-xs">Start Discussion</span>
</button>
</div>
</aside>
<!-- Mobile Edit Modal -->
<div id="edit-modal" class="fixed inset-0 z-[100] flex items-end md:hidden hidden">
<div onclick="closeEditModal()" class="absolute inset-0 bg-black/80 backdrop-blur-sm"></div>
<div class="relative w-full bg-slate-900 rounded-t-[2.5rem] p-6 pb-12 space-y-5 shadow-2xl max-h-[92vh] overflow-y-auto custom-scroll border-t border-white/10">
<div class="w-12 h-1.5 bg-slate-800 rounded-full mx-auto mb-2"></div>
<div class="flex justify-between items-center px-1">
<h3 id="mobile-edit-title" class="text-xs font-black text-indigo-400 uppercase tracking-widest">Character Setup</h3>
<button onclick="closeEditModal()" class="text-slate-500 text-3xl leading-none">×</button>
</div>
<div class="space-y-5">
<div>
<label class="text-[9px] font-black text-slate-500 uppercase mb-1.5 ml-1 block tracking-widest">Display Name</label>
<input type="text" id="mobile-edit-name" class="w-full p-4 bg-slate-800 border border-white/5 rounded-2xl text-white text-base font-bold outline-none focus:ring-2 focus:ring-indigo-500 shadow-inner">
</div>
<div>
<label class="text-[9px] font-black text-slate-500 uppercase mb-1.5 ml-1 block tracking-widest">Personality (System Prompt)</label>
<textarea id="mobile-edit-prompt" class="w-full p-4 bg-slate-800 border border-white/5 rounded-2xl text-white text-sm h-52 outline-none focus:ring-2 focus:ring-indigo-500 resize-none shadow-inner leading-relaxed"></textarea>
</div>
<button id="mobile-save-btn" class="w-full py-5 bg-indigo-600 text-white font-black rounded-2xl shadow-xl active:scale-[0.97] transition-all uppercase tracking-[0.2em] text-xs">Update & Save</button>
</div>
</div>
</div>
<script type="module">
import { initializeApp } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-app.js";
import { getAuth, signInAnonymously, onAuthStateChanged, signInWithCustomToken } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-auth.js";
import { getFirestore, doc, setDoc, getDoc } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js";
// Environment variables provided by Canvas
const firebaseConfig = JSON.parse(__firebase_config);
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getFirestore(app);
const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-multi-chat';
const apiKey = ""; // Canvas handles this at runtime
let isAuto = false;
let isThinking = false;
let turnTimer = null;
let currentIdx = 0;
let lastSpeakerIdx = -1;
let currentUser = null;
// Default Persona Setup
const charDefaults = [
{ id: "p1", name: "P1", short: "P1", color: "orange", prompt: "あなたは論理的で冷静なP1です。事実に基づいた分析を得意とし、丁寧な口調で話します。" },
{ id: "p2", name: "P2", short: "P2", color: "emerald", prompt: "あなたは社交的で明るいP2です。他者の意見を肯定し、ポジティブな解決策を提案します。" },
{ id: "p3", name: "P3", short: "P3", color: "zinc", prompt: "あなたは懐疑的で鋭いP3です。既存の意見に疑問を呈し、あえて反対の視点を持ち込みます。" },
{ id: "p4", name: "P4", short: "P4", color: "sky", prompt: "あなたはクリエイティブなP4です。常識に囚われない独創的なアイデアを出し、抽象的な話を具体化します。" },
{ id: "p5", name: "P5", short: "P5", color: "violet", prompt: "あなたは慎重で保守的なP5です。リスクを最小化することを重視し、実直な口調で懸念点を伝えます。" },
{ id: "gm", name: "GM", short: "GM", color: "indigo", prompt: "あなたは進行役のGMです。議論を要約し、次のステップや結論に向けて促します。" }
];
let state = { chars: JSON.parse(JSON.stringify(charDefaults)), messages: [] };
const colorStyles = {
orange: 'bg-orange-500', emerald: 'bg-emerald-500', zinc: 'bg-slate-700',
sky: 'bg-sky-500', violet: 'bg-violet-500', amber: 'bg-amber-500', indigo: 'bg-indigo-600'
};
const msgStyles = {
orange: 'bg-orange-50 text-orange-900 border-orange-100',
emerald: 'bg-emerald-50 text-emerald-900 border-emerald-100',
zinc: 'bg-white text-slate-900 border-slate-200',
sky: 'bg-sky-50 text-sky-900 border-sky-100',
violet: 'bg-violet-50 text-violet-900 border-violet-100',
amber: 'bg-amber-50 text-amber-900 border-amber-100',
indigo: 'mc-bubble text-white border-transparent shadow-indigo-200'
};
// --- AUTH ---
const initAuth = async () => {
if (typeof __initial_auth_token !== 'undefined' && __initial_auth_token) {
await signInWithCustomToken(auth, __initial_auth_token);
} else {
await signInAnonymously(auth);
}
};
onAuthStateChanged(auth, async (user) => {
if (user) {
currentUser = user;
await loadSettings();
}
});
// --- DATA OPS ---
async function loadSettings() {
if (!currentUser) return;
try {
const settingsDoc = await getDoc(doc(db, 'artifacts', appId, 'users', currentUser.uid, 'settings', 'config'));
if (settingsDoc.exists()) {
state.chars = settingsDoc.data().chars;
}
renderCharButtons();
syncEditFields(0);
} catch (e) { console.error("Load fail:", e); }
}
async function saveSettings() {
if (!currentUser) return;
document.getElementById('save-indicator-pc').classList.remove('hidden');
try {
await setDoc(doc(db, 'artifacts', appId, 'users', currentUser.uid, 'settings', 'config'), {
chars: state.chars,
updatedAt: Date.now()
});
showToast("Cloud Synced");
} catch (e) { console.error("Save fail:", e); }
finally { document.getElementById('save-indicator-pc').classList.add('hidden'); }
}
function showToast(text) {
const el = document.getElementById('save-status');
el.textContent = text;
el.style.opacity = "1";
setTimeout(() => el.style.opacity = "0", 2500);
}
// --- UI RENDERING ---
function renderCharButtons() {
const grid = document.getElementById('char-grid');
grid.innerHTML = '';
state.chars.forEach((char, idx) => {
const btn = document.createElement('button');
btn.id = `char-btn-${idx}`;
const isActive = idx === currentIdx;
btn.className = `flex flex-col items-center justify-center p-3 rounded-2xl border-2 transition-all duration-300 ${isActive ? 'border-indigo-500 bg-white/10 ring-4 ring-indigo-500/10 shadow-xl scale-105 z-10' : 'border-transparent bg-slate-800 md:bg-white/5 hover:bg-white/10'}`;
btn.onclick = () => {
currentIdx = idx;
renderCharButtons();
syncEditFields(idx);
if (window.innerWidth < 768) document.getElementById('edit-modal').classList.remove('hidden');
};
btn.innerHTML = `
<div class="w-9 h-9 rounded-full ${colorStyles[char.color]} flex items-center justify-center text-white text-xs font-black mb-1.5 shadow-lg">${char.short}</div>
<span class="hidden md:block text-[9px] font-black text-slate-400 uppercase truncate w-full text-center tracking-tighter">${char.name}</span>
`;
grid.appendChild(btn);
});
}
function syncEditFields(idx) {
const char = state.chars[idx];
document.getElementById('pc-edit-name').value = char.name;
document.getElementById('pc-edit-prompt').value = char.prompt;
document.getElementById('mobile-edit-name').value = char.name;
document.getElementById('mobile-edit-prompt').value = char.prompt;
}
async function saveChanges() {
const isMobile = window.innerWidth < 768;
const name = document.getElementById(isMobile ? 'mobile-edit-name' : 'pc-edit-name').value;
const prompt = document.getElementById(isMobile ? 'mobile-edit-prompt' : 'pc-edit-prompt').value;
state.chars[currentIdx].name = name;
state.chars[currentIdx].short = name.substring(0, 2).toUpperCase();
state.chars[currentIdx].prompt = prompt;
renderCharButtons();
if(isMobile) window.closeEditModal();
await saveSettings();
}
window.closeEditModal = () => document.getElementById('edit-modal').classList.add('hidden');
// --- AI LOGIC ---
async function fetchAI(char, context) {
const sys = `あなたは「${char.name}」です。性格設定: ${char.prompt}\n議論の文脈を理解し、キャラクターらしく発言してください。回答は日本語で、簡潔に(100字以内)まとめてください。`;
try {
const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-09-2025:generateContent?key=${apiKey}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ contents: [{ parts: [{ text: sys + "\nログ:\n" + context }] }] })
});
const data = await res.json();
return data.candidates?.[0]?.content?.parts?.[0]?.text || null;
} catch (e) { return null; }
}
async function runTurn() {
if (!isAuto || isThinking) return;
isThinking = true;
document.getElementById('thinking-ui').classList.remove('hidden');
let idx;
do { idx = Math.floor(Math.random() * state.chars.length); } while (idx === lastSpeakerIdx && state.messages.length > 1);
const char = state.chars[idx];
updateTalkingUI(idx);
const context = state.messages.slice(-10).map(m => `${m.name}: ${m.text}`).join("\n");
const result = await fetchAI(char, context);
if (result && isAuto) {
addMessage({ name: char.name, text: result.trim(), color: char.color });
lastSpeakerIdx = idx;
}
isThinking = false;
document.getElementById('thinking-ui').classList.add('hidden');
updateTalkingUI(-1);
if (isAuto) turnTimer = setTimeout(runTurn, 3500 + Math.random() * 2500);
}
function addMessage(msg) {
state.messages.push(msg);
const isGM = msg.name === 'GM';
const logs = document.getElementById('chat-logs');
const div = document.createElement('div');
div.className = `flex ${isGM ? 'justify-end' : 'justify-start'} opacity-0 translate-y-6 transition-all duration-700 mb-2`;
div.innerHTML = `
<div class="flex flex-col ${isGM ? 'items-end' : 'items-start'} max-w-[88%] md:max-w-[80%]">
${!isGM ? `<div class="text-[9px] font-black text-slate-400 ml-2 mb-1 uppercase tracking-widest">${msg.name}</div>` : ''}
<div class="px-5 py-3 rounded-2xl text-sm border shadow-sm leading-relaxed ${msgStyles[msg.color] || 'bg-white'} ${isGM ? 'rounded-tr-sm' : 'rounded-tl-sm'}">
${msg.text}
</div>
</div>
`;
logs.appendChild(div);
requestAnimationFrame(() => div.classList.remove('opacity-0', 'translate-y-6'));
logs.scrollTo({ top: logs.scrollHeight, behavior: 'smooth' });
}
function toggleBroadcast() {
isAuto = !isAuto;
const dot = document.getElementById('live-dot');
const status = document.getElementById('live-status-text');
const btn = document.getElementById('auto-text');
const mBtn = document.getElementById('mobile-start-btn');
if (isAuto) {
dot.classList.add('bg-red-500', 'animate-pulse');
dot.classList.remove('bg-slate-300');
status.textContent = "ON AIR";
status.classList.add('text-red-600');
btn.textContent = "Stop Session";
mBtn.textContent = "STOP";
mBtn.classList.replace('bg-indigo-600', 'bg-red-600');
runTurn();
} else {
clearTimeout(turnTimer);
dot.classList.remove('bg-red-500', 'animate-pulse');
dot.classList.add('bg-slate-300');
status.textContent = "OFFLINE";
status.classList.remove('text-red-600');
btn.textContent = "Start Discussion";
mBtn.textContent = "START";
mBtn.classList.replace('bg-red-600', 'bg-indigo-600');
updateTalkingUI(-1);
}
}
function updateTalkingUI(idx) {
state.chars.forEach((_, i) => {
const b = document.getElementById(`char-btn-${i}`);
if (b) b.classList.toggle('talking-ring', i === idx);
});
}
// Init
document.getElementById('mc-form').onsubmit = (e) => {
e.preventDefault();
const input = document.getElementById('mc-input');
const val = input.value.trim();
if (!val) return;
input.value = '';
addMessage({ name: 'GM', text: val, color: 'indigo' });
if (isAuto && !isThinking) { clearTimeout(turnTimer); setTimeout(runTurn, 500); }
};
document.getElementById('auto-toggle').onclick = toggleBroadcast;
document.getElementById('mobile-start-btn').onclick = toggleBroadcast;
document.getElementById('pc-save-btn').onclick = saveChanges;
document.getElementById('mobile-save-btn').onclick = saveChanges;
document.getElementById('clear-btn').onclick = () => { state.messages = []; document.getElementById('chat-logs').innerHTML = ''; };
initAuth();
renderCharButtons();
</script>
</body>
</html>
