Bir öğleden sonrada etkileşimli kurgu (interactive fiction) prototipi kur
Üç NPC'li oynanabilir bir sahneyi terminal CLI'ı üzerinden ayağa kaldır — bir oyun motoruna bağlanmadan yazı yinelemesi (iteration) yap.
Yazılı bir kadronun "birlikte durup durmadığını" test etmek için bir oyun motoruna ihtiyacın yok. Bir terminal + Moonborn sohbet (chat) API'si yazı yinelemesi için yeterlidir. Hızlıca üç NPC alır, ensemble (çok karakterli) oturumda konuştururken sen oynarsın, drift skorlarını gözlersin, kadro çalışmıyorsa öğleden sonra refine (incelt) edersin.
Bu rehber minimum bir Node.js komut satırı aracı (CLI) kurar, kadroyu yükler, paylaşılan bir oturum açar ve speaker: (konuşmacı) ön ekiyle hangi karakterin konuşacağını seçtiğin bir döngü yazarsın.
Bu rehberi bitirdiğinde
- Üç (veya daha fazla) NPC ile ensemble oturum açabileceksin.
- Bir terminalden
speaker: mesajbiçiminde oturumu sürebileceksin. - Yazı yinelemesi sırasında drift zarfını (envelope) yan yana izleyebileceksin.
- Hafıza (memory) ve konuşma kaydı (transcript) ile geri çağrılı (callback) sahne yapabileceksin.
- Motora geçtiğinde aynı oturum kimliğini (ID) taşıyabileceksin.
Ön koşul: Node.js + @moonborn/sdk + API anahtarı. En az üç persona (yoksa hızlıca İlk persona'yı baştan sona üret ile üret).
Minimum CLI
import Moonborn from '@moonborn/sdk';
import readline from 'node:readline';
const client = new Moonborn({ apiKey: process.env.MOONBORN_API_KEY });
// 1. Kadroyu yükle (önceden üretilmiş 3 persona).
const cast = {
merchant: await client.personas.get({ id: 'per_merchant...' }),
traveler: await client.personas.get({ id: 'per_traveler...' }),
tavernKeeper: await client.personas.get({ id: 'per_tavern...' }),
};
// 2. Tek bir paylaşılan oturum aç.
const session = await client.chat.sessions.create({
personaId: cast.tavernKeeper.id,
ensemble: [cast.merchant.id, cast.traveler.id],
metadata: { sceneId: 'tavern_prototype' },
});
// 3. CLI döngüsü.
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
function nameToPersona(name: string) {
const lower = name.toLowerCase().trim();
if (lower === 'merchant') return cast.merchant;
if (lower === 'traveler') return cast.traveler;
if (lower === 'tavern' || lower === 'keeper') return cast.tavernKeeper;
return cast.tavernKeeper; // varsayılan
}
function prompt() {
rl.question('You > ', async (text) => {
if (text.trim() === ':quit') return rl.close();
// "merchant: dışarlılara hizmet vermiyoruz" biçimi:
// speaker = merchant, content = "dışarlılara hizmet vermiyoruz"
// Hiç ön ek yoksa anlatıcı (narrator) modu; varsayılan konuşmacı tavernKeeper.
const [tag, ...rest] = text.split(':');
const speaker = rest.length > 0 ? nameToPersona(tag) : cast.tavernKeeper;
const content = rest.length > 0 ? rest.join(':').trim() : text;
const reply = await client.chat.messages.create({
sessionId: session.id,
speaker: speaker.id,
content,
});
console.log(`${speaker.surface.name.display} > ${reply.content}`);
if (reply.driftAlert) {
console.warn(` (drift: ${reply.driftScore.toFixed(2)})`);
}
prompt();
});
}
prompt();Tipik bir oturumun nasıl göründüğü
You > Selam, kim var?
Tavern Keeper > Bir köşede oturmuş yabancı. Buralı değil.
You > traveler: tavern keeper'a bakıyor. Bir yer var mı?
Traveler > "Bir yatak ve bir tas çorba istiyorum. Param yok ama elimde
bir mektup var."
You > tavern: traveler'ın yüzüne bak.
Tavern Keeper > "Mektup mu? Buralarda mektup ya bir yargıç ya bir
mezar habercisidir. Hangisini taşıyorsun?"
You > traveler: cebinden buruşmuş bir kâğıt çıkarır.
Traveler > "Yargıç değil. Çocuk için."
You > merchant: kapıdan içeri girer, omzunda bir çuval.
Merchant > "Tavernci! Bana bir kase çorba, bir ekmek—"
(drift: 0.32)Ne kazanırsın
| Kazanım | Neden önemli |
|---|---|
| Dakika ölçeğinde yineleme | Bir karakteri değiştir, hemen sahneyi tekrar oyna |
| Drift skorları yan yana | Sahne ısındıkça hangi karakterin kaymaya başladığını anında gör |
| Hafıza kalıcılığı (memory persistence) | Önceki turdaki olaylar sonraki turda hatırlanır; "geçen seferki mektup neydi?" sorusu doğru yanıtlanır |
| Hızlı kadro değişimi | Bir NPC'yi kaldır, başkasını koy — oturum kapatmadan |
Gerçek motora karşı ne kaybedersin
| Kayıp | Telafi |
|---|---|
| Oyuncu arayüzü (player UI) | CLI metni; motora geçtiğinde UI motorda kurulur |
| Dallı sahne durumu (branching scene state) | Sen doğrusal (linear) betik yazarsın; motor bunu kendi durum makinesinde (state machine) modeller |
| Kayıt / yükleme (oturum kimliği dışında) | Motor kayıt sistemine bağlanır; Moonborn oturum kimliği referans anahtarı (key) olur |
| Ses / animasyon | CLI yalnızca metin; motor metin-konuşma (TTS) ve animasyon katmanı ekler |
Motora "mezun olma"
Prototipleme aşaması bittiğinde:
- Oturum kimliklerini sakla — motor referans olarak kullanır.
- Persona kimlikleri kararlı (stable) kalır — motor bunlara aynen referans verir.
- Soyu (lineage) koruyarak motora aktar — prototipte ürettiğin varyantlar üretime (production) taşınır.
- CLI betiklerini motor sahne betiğine dönüştür — Twine, Ink, Unity diyalog betikleri tipik hedeflerdir.
Yineleme hızı için ipuçları
- Sıcak yeniden yükleme (hot reload) kadrosu —
node --watch run-cli.tsile dosya kaydedildikçe CLI yeniden başlar. Persona kimlikleri sabit kalır, kadro listesi anında güncellenir. - Konuşma kaydını dosyaya kaydet —
metadata.sceneId'yi bir dosya adına eşle (logs/${sceneId}-${timestamp}.txt), her çalıştırmayı (run) arşivle. Yazar incelemesinde geri dönüş kolaylaşır. - Hızlı sıfırlama komutu —
:resetkomutu yeni bir oturum açıp aynı kadroyla başlasın; sahneyi baştan denemek için.
İlgili
Ensemble oturum + konuşmacı seçimi adım adım eğitimi.
Prototipte ısındıktan sonra motor entegrasyonu desenleri.
Oyun ekiplerinin Moonborn'u motorla nasıl kullandığına dair kullanım senaryosu.
Prototipe başlamadan kadro yaratımı için.