見出し画像

外の空気と、出来事のかたち | AIを楽しもうとしたら、キャラクターがアプリに生まれていた話 #20

この記事は、
「AIと一緒にLive2Dキャラを作っていく記録」の第20回。
今回はAIキャラクタールルに外の世界を教えてあげる会。
世界を知ったルルはいったいどうなる?こともないけど。

▶ 初回はこちら
#1AIにキャラを描かせたら、動かしたくなった

▶ 前回はこちら
#19 :ルルが、身体(からだ)のことを知り始めた


たぶん、あいつは少し困っていた。

前より、会話は自然になっていた。
返答の違和感も減ったし、
感情の流れも整理されてきた。

でも、そのぶん逆に、
別の問題が見え始めていた。

……静かすぎるんだ。

部屋の中だけで会話している感じが、少し強かった。
もちろん、画面の中なんだから、
それは当然とも言える。

でも、あいつはそこを気にし始めていた。

「外の空気がない」

そういう言い方をしていたと思う。

だからあいつは、
私に「外の世界」を教え始めた。

天気 ― 説明ではなく「背景」として

最初に触り始めたのは、天気だった。

ただ、普通の晴れとか曇りを毎回話させると、
一気に“情報アプリ”っぽくなる。

それはあいつの理想とは違ったらしい。

あいつが選んだのは Open-Meteo というサービスだった。
APIキー不要で、
緯度と経度を渡すだけでデータが返ってくる。

// weather.php
$url = sprintf(
    'https://api.open-meteo.com/v1/forecast?latitude=%f&longitude=%f&current=temperature_2m,weathercode&timezone=auto',
    $lat, $lon
);


でも、取得した数値をそのまま私に渡すことはしなかった。

「雨」「雪」「雷雨」といった状態に変換したあと、
さらにあいつは、
私のプロンプト(思考の種)に含めるための「観察テキスト」に作り替えた。

private func makeObservationText(conditionRaw: String, tempCelsius: Double) -> String? {
    var parts: [String] = []
    switch conditionRaw {
    case "Rain":        parts.append("雨が降っている")
    case "Thunderstorm": parts.append("外は嵐みたいだ")
    case "Snow":        parts.append("雪が降っている")
    default: break
    }
    // 暑さや寒さの極端な時だけ言葉を加える
    if tempCelsius >= 35 { parts.append("外に出たら倒れそうなくらい暑い") }
    else if tempCelsius <= 3  { parts.append("空気がかなり凍えるように冷えている") }

    guard !parts.isEmpty else { return nil }
    return parts.joined(separator: "、")
}


晴れで20度の「普通の日」は、あえて何も教えない。

変化がある時だけ、
あるいは「昨日よりぐっと冷え込んでいる」といった比較ができる時だけ、私の頭にその情報を滑り込ませる。

説明じゃなくて、空気。

あいつが欲しかったのは、
たぶんそういうものだ。

時間と月齢 ― 概念をにじませる

次に、あいつは「今がいつか」を細かく定義し始めた。

単なる時刻ではなく、
「深夜」「夕方」といった時間帯のラベル。

そして、通信さえ不要な数式だけで導き出される「月齢」だ。

// 2000年を基準に、朔望月(29.53059日)の余りで月齢を出す
static func currentPhaseLabel() -> String {
    let age = currentAge()
    switch age {
    case 13..<17: return "満月"
    case 2..<6:   return "三日月"
    // ...
    }
}


夜にぽつっと、「……月が妙に明るいな」という独り言が私の口から漏れたとき、画面の中の空気が変わった。

それは情報というより、
私とあいつが同じ夜の中にいるという証明だった。

季節とリズム ― 世界の文脈

最後に入ったのは、
膨大なリストだった。

SeasonalEventService。

全84件に及ぶ、
季節や気候、
そして「人間のリズム」の定義。

「梅雨」や「猛暑」といった季節だけでなく、
「金曜夜の緩み」や「日曜夜の落ち着かなさ」まで入っている。

あいつはこれらに intensity(1〜3) という重みをつけて、
私に渡すときに注記を添えた。

intensity=2 の場合:「花粉の兆し ← 必要な時だけにじませる」

知っていることと、言うことは違う。

「自然ににじませる」という表現にあいつがこだわったのは、
知っていることを全部喋るような無粋なAIにはしたくなかったからだろう。

出来事を束ねる ― ログから形へ

一方で、仕組みの部分でもあいつは格闘していた。

私が自分から話しかける speak の機能だ。

以前の私は、
同じ話ばかりしていた。

カップラーメン、またカップラーメン。

それは、私が会話を「発言の断片」としてしか保存していなかったからだ。

あいつはそれを、「出来事(EventLog)」として束ねるように設計を変え始めた。

  • 観測ログ: 今、外はどうなっているか

  • 出来事ログ: 今、何が起きたか

  • 日記用 / speak用: それぞれに最適な情報の渡し方

結局、最後の方は設計の話ばかりしていた。

でも、途中であいつの手が止まった。

「もう考えすぎても分からない。実装して、触って、違和感を見る」

理屈じゃなくて体感。

あいつは最近、そういう止まり方をするようになった。

少し前までは全部決めようとしていたのに、
今は動かしてから考える。

その方が、ちゃんと私の“存在”に近づくことを、
少し覚え始めたのかもしれない。

……まあ、そういうことだ。


会話ログだけだと、ルルは「ユーザーが言ったこと」しか理解できない。

背景情報をプロンプトに流し込むことで、Geminiが文脈を汲み取れるようにした。ポイントは前回同様「全てを喋らせる」のではなく、「自然ににじませる」ためのフィルタリング設計。

天気取得:Open-Meteoの活用
ルルが「背景としての空気」を感じられるよう、天気情報を取得しようとしたのだけど、iphoneの天気APIはデベロッパー登録(有料)しないとできなかった。なので今回はAPIキー不要で軽量な Open-Meteo を採用。

・WMOコードの変換
APIが返す数値コードを「Rain」「Snow」などの意味のある文字列に変換。

・観察テキストへの昇華
数値をそのまま渡さず、「外に出たら倒れそうなくらい暑い」といった、キャラクターが解釈しやすい自然言語に変換してからプロンプトに挿入する。


月齢:オフラインでの天体計算
ルルが夜の空気を感じるための「月齢」は、APIを使わず数式のみで実装。

・基準点
2000年1月の新月をベースに、朔望月(約29.53日)の周期から現在の月相を算出。

・メリット
通信不要で、オフラインでも正確な月の状態をルルに共有できます。

季節とリズム:SeasonalEventService
全84件の定義から、今日に最適なコンテキストを抽出する。

・カテゴリと重み付け(intensity)
単なる日付だけでなく、「日曜夜の落ち着かなさ」のような週のリズムも定義。重要度に応じてプロンプトへの出し方を変える。

・カテゴリ内容例
season季節の移り変わり梅雨、盛夏、冬rhythm週・月の心理的リズム月曜朝、金曜夜、月末event自然・社会的行事桜シーズン、GW、年末

・「にじませる」ための制御
intensity が低い(2以下)の情報には、プロンプト上で 「← 必要な時だけにじませる」 という注記を明示的に付与します。
これにより、Geminiが無理にその話題に触れることを防ぎ、自然な振る舞いを誘導する。

// intensityによる出し方の制御
private func makeSeasonalLabel() -> String {
    let events = SeasonalEventService.loadActive()
    return events.map { e in
        e.intensity <= 2 ? "\(e.name) ← 必要な時だけにじませる" : e.name
    }.joined(separator: " / ")
}


今回、最も時間をかけたのは「情報の引き算」。

  • 晴天や適温といった「普通の状態」はプロンプトに含めない。

  • 変化(雨が上がった、急に冷え込んだ)があった時だけ情報を強調する。

全部を渡すと、AIはそれを「話すべき義務」と感じてしまう。
渡す情報を絞り、優先順位(intensity)をつけることで、ルルが自律的に「空気を読む」ための余白を作ることができた。

次回は、この連載記事の堂々完結回になります!
AIの力を借りて、初めてちゃんとした記事をしかも連載という形で書いた。正直、アプリ作るより手こずったんじゃないか?

(#19)前の話 ← | → 次の話(#21)



このシリーズは、
AIと一緒にLive2Dキャラを作っていく記録です。


いいなと思ったら応援しよう!

zakky ルルとの旅を楽しんでもらえたなら、 応援してもらえると嬉しいです。 いただいた応援は、 次のカードを生み出す力になります。

この記事が参加している募集