Geliştirici Günlüğü #2: Kilit adım ve senkron kaybı

Kısaca: MKA Engine'de çok oyunculu oyun kilit adımla (deterministic lockstep) çalışır: ağdan dünya değil yalnız oyuncuların komutları gider ve her bilgisayar aynı simülasyonu kendisi hesaplar. Bunun için motor her yerde aynı sonucu üretir; dünyanın 64 bitlik parmak izi bilgisayarlar arasında karşılaştırılır, fark varsa senkron kaybı çıktığı anda yakalanır.
Çok oyunculu strateji oynayanlar bu ekranı bilir: oyun "senkron kaybı" der ve oturum kopar. MKA Engine'de çok oyunculu oyunla ve tutarlılıkla ilgili her şeyi bu günlükte topladık: iki bilgisayarın aynı dünyayı nasıl hesapladığı, bunun için hangi kurallara uyduğumuz, bir fark çıkarsa nasıl yakalandığı ve biri oyundan düşerse ne olduğu. Yazının sonunda bunları 15 Ekim 2026'da yeniden koşturduğumuz testlerin sonuçları var.
Önce dürüst bir not: aşağıdakilerin hepsi motorun ekransız testlerinde ve ekransız sunucusunda çalışıyor. Ekranı çizen ön yüz henüz yazılmadı; yani bugün oynanabilir bir çok oyunculu oyun yok.
İki yol: dünyayı göndermek, hamleyi göndermek
Çok oyunculu bir oyunu kurmanın iki temel yolu var.
Birincisinde bir sunucu dünyayı hesaplar ve oyunculara dünyanın değişen kısmını sürekli gönderir. Nişancı ve aksiyon oyunlarının çoğu böyle çalışır (Unreal ağ yapısı (yeni sekmede açılır)). Ekranda birkaç düzine nesne olduğunda bu iyi bir yoldur.
İkincisi mektupla satranç oynamaya benzer: iki oyuncunun da önünde kendi tahtası vardır ve birbirlerine yalnız hamlelerini yazarlar. Tahtalar aynı başlar ve aynı hamleler aynı kuralla oynanırsa, iki tahta hep aynı kalır. Buna kilit adımlı oyun denir. Age of Empires yüzlerce birimi yavaş modem bağlantılarında bu yöntemle eşitliyordu (1500 Archers on a 28.8 (yeni sekmede açılır)).
| Ölçüt | Dünyayı göndermek | Hamleyi göndermek (kilit adım) |
|---|---|---|
| Ağdan ne geçer | Değişen nesnelerin durumu | Yalnız oyuncu komutları |
| Kim hesaplar | Sunucu | Her oyuncunun bilgisayarı |
| Dünya büyüdükçe ağ yükü | Büyür | Değişmez |
| En büyük şartı | Hızlı ve güçlü bir sunucu | Her bilgisayarın bit bit aynı sonucu üretmesi |
Bir büyük strateji oyununda 30.000 konum, 250 ülke ve 2 milyon nüfus birimi her gün değişir; bunu her gün her oyuncuya göndermek mümkün değildir. Oysa oyuncuların verdiği komutlar ("bu orduyu oraya yürüt", "vergiyi artır") birkaç bayttır. MKA Engine bu yüzden kilit adımlıdır. Bedeli tablonun son satırında: her bilgisayar aynı komutlardan bit bit aynı dünyayı çıkarmak zorundadır.
Kilit adım nasıl işler?

Oyunu bir grup yürüyüşü gibi düşünün: grup en yavaş yürüyeni bekler. Motorda her adım (bir gün ya da bir tur) şöyle ilerler:
- Bir oyuncu bir komut verdiğinde komut hemen uygulanmaz; iki adım sonrası için bir pakete konur ve diğer oyunculara gönderilir. Bu iki adımlık pay, komutun ağdan herkese ulaşması için zaman kazandırır.
- Bir adım, ancak her oyuncunun o adım için paketi geldiğinde koşulur. Paketi gelmeyen oyuncu varsa herkes onu bekler.
- Paketler herkeste aynı sırayla (oyuncu ve sıra numarasıyla) birleştirilir; böylece herkes aynı komutları aynı sırada uygular.
Oyunu duraklatmak bile bir komuttur: duraklatma kararı komut günlüğünden geçer, yani her bilgisayar aynı adımda durur.
Bağlantılar yıldız biçimindedir: bir oyuncu başka bir oyuncuya mesaj gönderdiğinde mesaj ana makineden geçer. Telden geçenler sınırlıdır:
| Geçer | Geçmez |
|---|---|
| Bir adımın komut paketi | Dünyanın durumu |
| Belirli aralıklarla dünyanın parmak izi | Nüfus, para, ordu gibi tek tek değerler |
| Katılma, ayrılma ve koltuk mesajları | Ekran görüntüsü, kare bilgisi |
| Oyuna sonradan girene kayıt dosyası | Zar sonuçları |
Zar sonuçları da geçmez; çünkü zar, Günlük #1'de anlattığımız gibi tohum ve adresle her bilgisayarda aynı çıkar.
Aynı sonuç için hangi kurallara uyuyoruz?
"Bit bit aynı" hedefi tek bir önlemle tutmaz; motorun çekirdeğinde bir dizi kurala uyuyoruz. Her biri, farklı bilgisayarlarda farklı sonuç çıkaran bilinen bir sebebi kapatıyor:
| Kural | Neyi önler |
|---|---|
| Ondalıklı sayı yok, yalnız sabit noktalı sayı | Derleyici ve işlemci farkından gelen küçük sapmalar |
| Zar yalnız tohum ve adresle | Zarların hangi sırayla atıldığının sonucu değiştirmesi |
| Zaman yalnız oyun adımı; saat, dosya ve ağ çekirdeğe giremez | Bilgisayarın hızının ya da saatinin oyuna sızması |
| Karma tablosu yok; veri hep aynı sırayla dolaşılır | Aynı verinin farklı bilgisayarda farklı sırayla işlenmesi |
| İş parçacıklarını yalnız zamanlayıcı açar; sonuçlar parça sırasıyla birleşir | Hangi çekirdeğin önce bittiğinin sonucu değiştirmesi |
| Varlık ekleme ve silme yalnız adım sınırında | Bir adımın ortasında dünyanın değişmesi |
İlk üç kuralı Günlük #1 ayrıntısıyla anlatıyor. Diğer üçü motorun veri deposunun ve zamanlayıcısının işi; onları kendi günlüklerinde anlatacağız.
Parmak izi: farkı yakalamak

Kurallara uysak bile bir hata çıkabilir: bir modun beklenmedik bir davranışı ya da motorda bir yanlış. Önemli olan, farkın çıktığı anda yakalanması. Bunun için dünyanın parmak izini alıyoruz: bütün durumdan hesaplanan 64 bitlik kısa bir sayı. Dünyada tek bir değer değişse parmak izi de değişir.
Parmak izi bir ağaç gibi kurulur: her tablonun satırları 16.384'lük parçalara bölünür, her parçanın kendi izi, her tablonun bir kök izi, bütün dünyanın da tek bir kök izi vardır. Ekransız sunucuda bilgisayarlar her 30 adımda bir dünya kök izini birbirine gönderir. İz tutmazsa motor bir "ayrışma" olayı üretir.
Sonra farkın yerini bulur. Bunu kütüphanede kaybolan bir kitabı aramaya benzetebilirsiniz: önce hangi salonda olduğuna, sonra hangi rafta, en son hangi sırada olduğuna bakarsınız. Motor da iki bilgisayarın izlerini önce tablo tablo, sonra parça parça, en son satır satır karşılaştırır ve ilk farkı "şu tablo, şu satır, şu varlık" diye bildirir. Bu arama sırasında da ağdan yalnız izler geçer; dünyanın kendisi gönderilmez.
Parmak izi bir değişikliği yakalamak içindir, şifreleme amaçlı değildir. İzin hesap yöntemi dondurulmuştur: kayıtlar, tekrar oynatmalar ve ağ denetimi aynı izleri karşılaştırdığı için bu yöntem değişirse hepsi bozulur.
Kanıt: 15 Ekim'de yeniden koşturduk
Bu günlüğü yazarken dört sınamayı yeniden koşturduk. Hepsi geliştirme bilgisayarımızda ve ekransız; süreler başka işlerle yüklü bir bilgisayarda alındığı için burada süre değil sonuç önemli.
- 10 yıl ölçek dünyası; günlük parmak izleri 1, 8 ve 16 iş parçacığında birebir aynı
- 5. yıl sonu kaydedilen oyun, yüklenince kesintisiz oyunla aynı
- 4 eş, 100 yıl ağ üzerinden hep aynı izlerle koşan oyun
Çekirdek sayısı fark etmiyor. Ölçek dünyasını (30.000 konum, 250 ülke, 2 milyon nüfus birimi) 10 oyun yılı boyunca üç kez koşturduk: tek iş parçacığında, 8 iş parçacığında ve 16 iş parçacığında. Her gün alınan parmak izlerinin üçü de birebir aynı çıktı. Yani 4 çekirdekli bir bilgisayarla 16 çekirdekli bir bilgisayar aynı oyunda buluşabilir.
Kaydet ve yükle aynı oyunu sürdürüyor. Aynı dünyada oyunu 5. yılın sonunda kaydettik, kaydı yükledik ve 5 yıl daha koşturduk. Sonuç, hiç durmadan koşan oyunla aynıydı. Oyuna sonradan katılan oyuncu da bu yolla girer.
Dört bilgisayar, yüz yıl. Ağ testinde dört eş 100 oyun yılı koşturuyor: bir kez dört ayrı program olarak gerçek ağ bağlantısı (TCP) üzerinden, bir kez tek program içinde. Eşlerden birine yapay gecikme veriliyor. Her eş her denetimde aynı parmak izinde buluşuyor ve hiçbiri diğerlerinden girdi gecikmesinden (iki adım) fazla öne geçmiyor. İki koşu da geçti.
Farklı işlemciler aynı sonucu veriyor. Motorun çekirdek paketlerinin testlerini üç ayrı ortamda koşturduk: geliştirme bilgisayarımızın işlemcisi (x86_64), ARM işlemci (aarch64; Docker içinde öykünmeyle) ve WebAssembly (wasmtime ile). Üçü de aynı sonuçları verdi. Yani üç ortam aynı sayıları, zarları ve tarihleri üretti.
Aynı oyunu başlatmak: lobi
Bit bit aynı dünya, aynı başlangıçla başlar. Bir oyuncu lobiye katılırken ana makine şunları karşılaştırır: ağ protokolünün sürümü, motorun sürümü, yüklü modların özeti ve modların yükleme sırası. Biri tutmazsa katılma reddedilir ve iki taraf da kendi değerini görür; "bende şu mod var, sende yok" türünden bir sorun oyun başlamadan ortaya çıkar. Kayıt dosyası da mod özetini taşır, yani bir kayıt farklı bir mod setiyle açılamaz.
Oyun başlarken her bilgisayar aynı paketi alır: tohum, başlangıç adımı, koltuklar (hangi oyuncu hangi ülke) ve oyun kuralları. Aynı ülkeyi iki oyuncu isterse önce katılan alır.
Biri düşerse
İnternet bağlantısı kopar, bilgisayar donar. Motorun bunlara verdiği cevaplar:
| Durum | Ne olur |
|---|---|
| Bir oyuncu düşer | Oyun duraklar; belirlenen süre (0, 30, 60 ya da 120 saniye) dolunca o ülkeyi yapay zekâ devralır |
| Düşen oyuncu geri gelir | Kendi koltuğunun jetonuyla yeniden bağlanır; ülkesi ona geri verilir |
| Yeni biri oyuna katılır | Ana makine ona son kaydı ve sonraki komutları gönderir; kayıt 64 KB'lık parçalarla gider ve izi doğrulanır |
| Ana makine ayrılır | Herkes son adımda durur; kalanlar arasında en küçük oyuncu numarası kendi kaydından oyunu sürdürür |
| Duraklatma | Üç kural seçeneği: herkes duraklatır, yalnız ana makine duraklatır, ya da ana makinenin duraklatmasını yalnız o kaldırır |
| İki insan oyuncu savaşta | Ana makine oyunun hızına bir tavan koyabilir |
Ayrıca ekransız bir sunucu programımız var: hiçbir ülkeyi oynamayan, yalnız oyunu barındıran bir ana makine. Gözlemciler de katılabilir; gözlemcinin komutları reddedilir.
Sınırlar
- Oynanabilir oyun yok: Bunların hepsi ekransız testlerde ve sunucuda çalışıyor; ekran ve arayüz henüz yok.
- En yavaş belirler: Kilit adımda oyun en yavaş bilgisayarın ve en yavaş bağlantının hızında ilerler.
- İki adımlık gecikme: Bir komut, verildiği adımdan iki adım sonra uygulanır.
- Ana makine değişimi kesintisiz değil: Ana makine ayrılınca oyun kısa bir süre durur ve kayıttan devam eder.
- Her şey ana makineden geçer: Yıldız düzeninde ana makinenin bağlantısı herkesin bağlantısını etkiler.
- Hile önleme değil: Her bilgisayar dünyanın tamamını hesapladığı için, değiştirilmiş bir istemci normalde görmemesi gereken bilgiyi okuyabilir. Kilit adım bunu kendi başına önlemez.
- Oyuncu bulma yok: İnternet üzerinden oyuncu bulma ve bağlantı kurma, oyunun yayımlanacağı platformun işidir; henüz yazılmadı.
Sık sorulanlar
Kilit adım (lockstep) nedir?
Her bilgisayarın aynı simülasyonu koştuğu, ağdan yalnız oyuncu komutlarının gittiği çok oyunculu yöntem. Büyük dünyalı strateji oyunlarında ağ trafiğini dünya büyüklüğünden bağımsız tutar.
Senkron kaybı (desync) neden olur?
İki bilgisayar aynı komutlardan farklı bir dünya hesapladığında. Tipik sebepler ondalıklı sayı farkları, sıralaması belirsiz tablolar, saate ya da ortama bağlı kod ve iş parçacıklarının yarışmasıdır.
MKA Engine senkron kaybını nasıl yakalıyor?
Dünyanın bütün durumundan 64 bitlik bir parmak izi hesaplanır ve oyuncular arasında karşılaştırılır. Fark çıkarsa oyun hemen bunu bildirir.
Oyundan düşen oyuncu geri dönebilir mi?
Evet. Düşen oyuncu kendi koltuğunun jetonuyla yeniden bağlanır ve ülkesi ona geri verilir. Yeni katılan birine ana makine son kaydı ve sonraki komutları gönderir. Bugün bu, motorun ekransız sunucusunda çalışıyor.
Kaynaklar ve kayıtlar
Dış kaynaklar:
- Paul Bettner, Mark Terrano, 1500 Archers on a 28.8: Network Programming in Age of Empires and Beyond (yeni sekmede açılır)
- Epic Games, Unreal Engine ağ yapısına genel bakış (yeni sekmede açılır)
- Vikipedi, Lockstep protocol (yeni sekmede açılır)
- Glenn Fiedler, Floating Point Determinism (yeni sekmede açılır)
İç kayıtlarımız (motorun deposu kapalı olduğu için bağlantı veremiyoruz):
- Belirlenimcilik kararı ve lobi/el değiştirme kararı.
- Çok oyunculu katman belgesi: tel protokolü, lobi, duraklatma, kayıtla el değiştirme, ayrışma yerelleştirme ve ekransız sunucu.
- 15 Ekim 2026 sınamaları: ölçek dünyası 10 yıl, 1/8/16 iş parçacığı ve günlük parmak izleri; 5. yıl sonunda kaydet-yükle karşılaştırması; dört eşle 100 yıllık ağ testi (iki koşu); çapraz test (x86_64, aarch64, WebAssembly).
Bir sonraki günlükte motorun içine iniyoruz: 2 milyon nüfus birimi bellekte nasıl tutulur?
Bu günlük hakkında sorularınız ya da önerileriniz varsa bize iletişim sayfasından yazabilirsiniz. Motorun genel tanıtımı için MKA Engine sayfasına, bütün günlükler için Geliştirici Günlükleri listesine bakabilirsiniz.