T: tema · Z: kodu büyüt · S: konuşmacı notları · F: tam ekran · Esc: genel bakış · ⌂ ders haritası
OYUN GEOMETRİSİ VE FİZİĞİ · G7
Ağ Senkronizasyonu
Yetkili sunucu, ara değerleme, istemci tahmini ve uzlaştırma, gecikme telafisi, eşzamanlı simülasyon, geri sarma — “herkes aynı oyunu mu görüyor?”
PRESS ▶ TO START

Bu derste oyunun haritası 🗺️

  1. Sorun: gecikme — ışık hızı bile yetmiyor; kime güveneceğiz? Yetkili sunucu
  2. Ara değerleme — saniyede 20 paketten akıcı rakipler: bilerek geçmişi göstermek
  3. Tahmin ve uzlaştırma — kendi karakteriniz neden anında kıpırdıyor; sunucu “hayır” deyince ne oluyor?
  4. Gecikme telafisi — ekranda vurduğunuz sayılsın diye sunucu zamanı geri sarar
  5. Eşzamanlı simülasyon ve geri sarma — strateji oyunlarında yalnızca girdileri yollamak; dövüş oyunlarında tahmin et, tutmazsa geri sar
  6. Her adımda — elle izleme → canlı demo → kod (JavaScript / Python / C#) → gerçek oyun kaynak kodu
🎮 Bu dersin sonunda…

çevrimiçi bir oyunun gecikmeyi oyuncudan saklamak için kullandığı beş tekniği açıklayabilecek, tek makinede sahte bir ağ kurup her birini yazabilecek ve “vurdum ama saymadı”, “köşeyi dönmüştüm ama öldüm”, “lastik gibi geri çekildim” şikâyetlerinin hangisinden kaynaklandığını söyleyebileceksiniz.

🤔 ISINMA: “KÖŞEYİ DÖNMÜŞTÜM AMA VURULDUM!”

Çevrimiçi nişancı oyunu oynayan herkes yaşamıştır: sipere girdiniz, güvendesiniz; yarım saniye sonra ölüyorsunuz. Öldüren oyuncunun kaydına bakıyorsunuz: sizi açık alanda, tam ortada vurmuş. İkiniz de doğru görüyorsunuz. Nasıl?

  • Aynı anda iki farklı “şimdi” var. Sizin ekranınız ile onun ekranı arasında 100–200 milisaniye fark var; ikisi de sunucunun gerçeğinden farklı.
  • Oyun bunu bilerek yapıyor: oyuncuların hiçbiri gecikmeyi hissetmesin diye herkese biraz farklı bir zaman gösteriyor.
  • Bedeli, zamanlar çakıştığında ortaya çıkan bu tür “haksızlıklar”. Geliştirici hangisinin kime yaşatılacağını seçer.
  • Bu sunum o seçimleri anlatıyor. Araçların hepsini biliyorsunuz: lerp (G6), sabit adımlı simülasyon (G5), ışın testi (G3), ikili arama, kuyruk.

🎯 Bu konu için bilmeniz gerekenler

Yeşil = gerekli · kesikli = faydalı ama şart değil. Tıklayınca ilgili sunuma gider.

Neden?G5’te “aynı girdiler aynı sonucu versin” diye sabit adımı kurduk; G6’da iki değerin arasını doldurmayı öğrendik. Ağ kodu bu ikisinin üstüne oturur: simülasyonu yeniden oynatmak ve paketlerin arasını doldurmak.
🧰 Kodlamadan korkuyorsanızBu sunumda soket, protokol, IP adresi yok. Bütün demolar tek sayfada çalışır: “ağ”, mesajları bir süre bekleten bir kuyruktur. Gerçek ağa geçmek, o kuyruğu bir kütüphaneyle değiştirmektir; zor olan kısım burada anlattıklarımız.

Mini sözlük

latency · ping · RTTgecikme · — · gidiş-dönüş süresiMesajın karşıya varma süresi · gidiş-dönüşün ölçüsü
tick ratesunucu adım hızıSunucunun saniyede kaç kez dünyayı ilerletip yayınladığı
authoritative serveryetkili sunucuOyunun tek gerçeğini tutan taraf; istemciler yalnızca girdi yollar
snapshotanlık görüntüSunucunun bir andaki dünya durumunu taşıyan paket
entity interpolationvarlık ara değerlemesiBaşka oyuncuları iki anlık görüntü arasında, biraz geçmişte çizme
client-side predictionistemci tahminiKendi girdini, sunucu onayını beklemeden yerelde uygulama
server reconciliationsunucu uzlaştırmasıSunucu durumuna dönüp onaylanmamış girdileri yeniden uygulama
lag compensationgecikme telafisiSunucunun atışı sınarken hedefleri nişancının gördüğü ana geri sarması
lockstep · rollbackeşzamanlı simülasyon · geri sarmaYalnızca girdileri paylaşıp aynı simülasyonu koşturma · tahmin tutmazsa geçmişe dönüp yeniden simüle etme
desynceşzamansızlıkİki makinenin simülasyonlarının birbirinden ayrışması
LEVEL 0
Sorun: gecikme
ve kime güveneceğimiz

Problem: “şimdi” herkes için farklı

🎮 Bir mesajın yolculuğu
  • İstanbul’daki oyuncu tuşa bastı. Mesaj Frankfurt’taki sunucuya gidecek: yaklaşık 1.900 km.
  • Işık fiberde saniyede ≈ 200.000 km yol alır: yalnızca kablo için en az 10 ms. Yönlendiriciler, kablosuz ağ, yoğunluk: gerçekte 25–40 ms.
  • Sunucu mesajı bir sonraki adımında işler (saniyede 60 adım: 0–17 ms bekleme), sonucu yayınlar.
  • Sonuç geri döner: bir 25–40 ms daha. Ekran onu bir sonraki karede çizer.
  • Toplam: tuştan görüntüye 70–120 ms. 60 FPS’te 4–7 kare.

İnsan, kendi hareketine verilen tepkide 50–100 ms’yi “gecikme” olarak hisseder.

İki naif çözüm, iki felaket

1) Herkes kendi konumunu söylesin. Her istemci kendi karakterini yürütür ve “buradayım” diye yayınlar.

  • Hile yapmak tek satır: “buradayım” yerine “düşmanın arkasındayım”, “canım 9999”.
  • İki oyuncu aynı altını aynı anda aldı: kim haklı?

2) Her şeye sunucu karar versin, istemci yalnızca göstersin.

  • Güvenli ve tutarlı. Ama tuşa basıyorsunuz, karakter 100 ms sonra kıpırdıyor; rakipler saniyede 20 kare zıplıyor.
  • 1996’daki ilk Quake internet üzerinde tam böyle oynanıyordu; yalnızca yerel ağda keyifliydi.

Çözüm ikincisinden vazgeçmek değil; gecikmeyi oyuncudan saklamak.

Çizimle: kim neyi, ne zaman görüyor?

🖥️ SUNUCUoyunun tek gerçeği burada🎮 Sizekranda: tahmin edilen gelecek🎮 Rakipsizin ekranınızda: ≈ 100 ms geçmişigirdi: “sağa bastım”durum: herkesin konumugirdizamanrakibi burada görüyorsunuzgeçmiş (ara değerleme)sunucunun “şimdi”sikendinizi burada görüyorsunuzgelecek (tahmin)

İstemciler sunucuya yalnızca girdi yollar; sunucu oyunu yürütür ve herkese durumu yayınlar. Mesajlar yolda zaman kaybettiği için ekranınızda aynı anda iki ayrı zaman vardır: rakipler biraz geçmişte, siz biraz gelecekte.

Fikir: yetkili sunucu + üç “yalan”

  • Yetkili sunucu: oyunun tek gerçeği sunucudadır. İstemciler durum değil girdi yollar (“sağa bastım”, “ateş ettim”); sunucu girdiyi doğrular, simülasyonu yürütür, sonucu herkese yayınlar.
  • İstemcinin gösterdiği her şey bir tahmindir; sunucu ne derse o olur.
  • Gecikmeyi saklamak için istemci (ve sunucu) üç ayrı numara yapar:
kim içinnumaraekranda gösterilen zaman
başka oyuncularara değerlemebiraz geçmiş (≈ 100 ms önce)
kendi karakteriniztahmin + uzlaştırmabiraz gelecek (sunucunun henüz bilmediği)
atışlarınızgecikme telafisisunucu, sizin gördüğünüz geçmişe döner

Yani ekranınızda aynı anda iki farklı zaman var: siz gelecekte, herkes geçmişte. Isınmadaki “köşe” olayı bu ikisinin çarpışmasıdır.

Sayılarla bir nişancı oyunu
ayartipik değeranlamı
sunucu adım hızı20–128 HzQuake III 20; CS:GO 64; Valorant 128; Apex Legends 20
anlık görüntü aralığı8–50 ms128 Hz’te 7,8 ms; 20 Hz’te 50 ms
ara değerleme tamponu≈ 2 paket aralığıSource motorunda varsayılan 100 ms
gidiş-dönüş (ping)20–150 msaynı bölge 20–50; kıtalar arası 150+
paket kaybı%0–5kablosuz ağda ani sıçramalar

Bant genişliği de sınırlı: 64 oyuncu × 64 Hz × oyuncu başına 50 bayt = saniyede 200 KB, her istemciye. Sunucular yalnızca değişeni (“delta”) ve yalnızca görülebileni (G4: görünürlük) yollar.

LEVEL 1
Ara değerleme
saniyede 20 paketten akıcı rakipler

Problem: paketler seyrek, ekran sık

🎮 20 paket, 144 kare
  • Sunucu rakibin konumunu saniyede 20 kez yolluyor: 50 ms’de bir.
  • Ekranınız saniyede 144 kare çiziyor: 7 ms’de bir.
  • İki paket arasında 7 kare var ve elinizde o kareler için veri yok.

Ham gösterim: yeni paket gelene kadar rakibi olduğu yerde tut. Rakip 20 FPS’lik bir film gibi zıplar; nişan almak imkânsız.

İlk akla gelen: ileriye tahmin (dış değerleme)

Son iki paketten hızı bul; rakibi o hızla ileri yürüt.

  • Rakip düz gidiyorsa mükemmel: hem akıcı hem güncel.
  • Rakip dönünce ya da durunca tahmin yanlış yöne devam eder; yeni paket gelince rakip geri “çekilir”.
  • Nişancı oyununda oyuncular saniyede birkaç kez yön değiştirir (tam da vurulmamak için). Rakip sürekli titrer.
  • Araçlar, gemiler, mermiler gibi ataleti olan şeylerde iyi çalışır; koşan insanda çalışmaz.

Başka bir yol: tahmin etme. Elindeki iki gerçek paketin arasını doldur. Bunun için ikinci paketin gelmiş olması, yani biraz geride kalmak gerekir.

Çizimle: iki paketin arasını doldurmak

1001101201301.000 ms1.050 ms1.100 ms1.175 mspaket A: x = 100paket B: x = 110paket C: x = 130şimdi (tahmin)çizilen an: 1.175 − 100 = 1.075 mstampon: 100 ms geriB ile C’nin tam ortası → x = 120rakibinx konumu

Sunucudan saniyede yalnızca 20 konum geliyor (mavi noktalar). Rakibi “şimdi”de değil, 100 ms önceki anda çizeriz: o an hep iki paketin arasına düşer ve konum ikisinin arasından lerp ile bulunur. Sonraki slayt aynı sayıları tabloyla veriyor.

Fikir: bilerek geçmişi göster

  • Gelen paketleri zaman damgalarıyla bir tamponda sakla.
  • Rakibi “şimdi”de değil, şimdi − tampon süresi anında çiz.
  • O an, tampondaki iki paketin arasına düşer: ters lerp ile oranı bul, lerp ile konumu (G6).
  • Tampon süresi ≈ iki paket aralığı: bir paket kaybolsa bile iki yanında paket olur.
  • Sonuç: rakip her zaman gerçekten bulunduğu noktalardan geçer; titreme ve geri çekilme yok. Bedeli: gördüğünüz rakip tampon + gecikme kadar eski.

Paket hızı arttıkça tampon kısalır: 128 Hz’te iki paket 16 ms eder. Yüksek “tick rate”in asıl faydası budur.

Elle izleme: 20 Hz, tampon 100 ms
paketsunucu zamanırakibin x’i
A1.000 ms100
B1.050 ms110
C1.100 ms130
  • İstemcinin sunucu saati tahmini: 1.175 ms.
  • Çizilecek an: 1.175 − 100 = 1.075 ms.
  • 1.075, B (1.050) ile C (1.100) arasında.
  • Oran: (1.075 − 1.050) / (1.100 − 1.050) = 0,5.
  • Konum: lerp(110, 130, 0,5) = 120.

Bir sonraki kare (7 ms sonra): an = 1.082 → oran 0,64 → x = 122,8. Paket gelmeden rakip 2,8 birim kaydı.

C paketi kaybolsaydı? 1.075 anı için B ile bir sonraki paket (D, 1.150) arasında lerp edilirdi: tampon bunun için iki paketlik.

Canlı: ham, ara değerleme, dış değerleme 📡

  1. Ekranı okuyun: en üstteki sarı top sunucudaki gerçek; altındaki üç top aynı paketleri üç farklı yöntemle gösteren istemciler. Uçan küçük kareler yoldaki paketler. Alttaki grafik son 4 saniye: sarı kesikli = gerçek.
  2. Kırmızı (ham): grafikte merdiven. Paket hızını 4’e indirin: basamaklar büyür (yeşil de takılır: 150 ms’lik tampon artık bir paket aralığından kısa). 60’a çıkarın: neredeyse pürüzsüz, ama bant genişliği 15 katı.
  3. Yeşil (ara değerleme): sarı eğrinin sağa kaymış kopyası: aynı biçim, biraz geç. Kayma = gecikme + tampon.
  4. Mor (dış değerleme): düz giderken sarının tam üstünde (güncel!); dönüş anlarında sivri taşmalar. Gecikmeyi 250 ms yapın: taşmalar büyür.
  5. Kaybı %20 yapın. Tampon 150 ms iken yeşil hâlâ pürüzsüz. Tamponu 40’a indirin (bir paket aralığından az): “⚠️ tampon boş” uyarısı ve takılmalar.
  6. Alttaki ortalama hatalara bakın: en “hatalı” görünen yeşil, en rahat izlenendir. Doğruluk ile akıcılık aynı şey değil.
🔎 DEMODA DENEYİN
  1. Ekranı okuyun: en üstteki sarı top sunucudaki gerçek; altındaki üç top aynı paketleri üç farklı yöntemle gösteren istemciler. Uçan küçük kareler yoldaki paketler. Alttaki grafik son 4 saniye: sarı kesikli = gerçek.
  2. Kırmızı (ham): grafikte merdiven. Paket hızını 4’e indirin: basamaklar büyür (yeşil de takılır: 150 ms’lik tampon artık bir paket aralığından kısa). 60’a çıkarın: neredeyse pürüzsüz, ama bant genişliği 15 katı.
  3. Yeşil (ara değerleme): sarı eğrinin sağa kaymış kopyası: aynı biçim, biraz geç. Kayma = gecikme + tampon.
  4. Mor (dış değerleme): düz giderken sarının tam üstünde (güncel!); dönüş anlarında sivri taşmalar. Gecikmeyi 250 ms yapın: taşmalar büyür.
  5. Kaybı %20 yapın. Tampon 150 ms iken yeşil hâlâ pürüzsüz. Tamponu 40’a indirin (bir paket aralığından az): “⚠️ tampon boş” uyarısı ve takılmalar.
  6. Alttaki ortalama hatalara bakın: en “hatalı” görünen yeşil, en rahat izlenendir. Doğruluk ile akıcılık aynı şey değil.

🔗 Başka gözle: etkileşimli anlatımlar ve yazılar

Aynı konuyu başka bir anlatımla, kendi elinizle oynayarak görmek çok işe yarar. Kartlar yeni sekmede açılır; çoğu İngilizce ama görsel ve etkileşimli.

Kod: anlık görüntü tamponu ve ara değerleme

🎒 Kodu okuyalım

ifadene demekneden böyle
paketler.push(p) + shift()zaman damgalı tampon; eskiler atılırBir kuyruk (Yığınlar ve Kuyruklar). Paketler sırasız gelebilir: gerçek kodda zaman damgasına göre araya eklenir.
an = sunucuSimdi - TAMPONçizilecek an: bilerek geçmişYöntemin tamamı bu çıkarma. TAMPON ≈ 2 paket aralığı.
a.t <= an && an <= b.tan’ı içine alan iki komşu paketSondan başa aranır: aranan an neredeyse hep son iki-üç paketin arasındadır.
(an - a.t) / (b.t - a.t)ters lerpPaket aralığı sabit olmak zorunda değil; kayıp varsa aralık 100 ms olur, formül aynen çalışır.
return paketler[son]tampon boşaldıİki seçenek: bekle (donma) ya da kısa süre dış değerleme yap (Source: en çok 250 ms). İkisi de çirkin; tamponu büyütmek daha iyi.
sunucuSimdiistemcinin sunucu saati tahminiİki makinenin saati aynı değildir. İstemci, paket zaman damgalarından sunucu saatini kestirir ve yumuşatır.
ÖnermeAra değerlemeyle çizilen bir varlık, sunucudaki gerçek konumunun gecikme + tampon kadar gerisindedir ve (paket kaybı tamponu aşmadıkça) yalnızca gerçekten bulunduğu noktalar arasındaki doğrular üzerinde görünür. v hızıyla koşan bir rakip için konum farkı v × (gecikme + tampon)’dur: 250 birim/sn ve 150 ms için 37,5 birim.

Oyunlarda: Quake III’ün ara değerlemesi

Kaynak: id Software, Quake III Arena, cg_ents.c (GPL). İngilizce yorumlar özgün koddan (yazım hatasıyla birlikte).

🎮 Satır satır
  • cg.snap ve cg.nextSnap: tampondaki iki komşu anlık görüntü. İkincisi yoksa bu fonksiyon çağrılmamalıdır: “iç hata”.
  • cg.frameInterpolation: bizim ters lerp’imiz; iki görüntünün zaman damgaları arasındaki oran.
  • current + f * (next − current): üç eksen için üç lerp. Açılar için ayrı LerpAngle (350° → 10° sorunu, G6).
  • Yorumdaki cümle bu bölümün özeti: “daha yeni veri varken oyuncu konumlarını dış değerlememek önemlidir.”
  • Varsayılanlar (kaynak kodundan): sunucu sv_fps 20, istemcinin istediği görüntü hızı snaps 20, saniyede en çok cl_maxpackets 30 girdi paketi. 1999’un modemleri için.
  • İstisna: cg_smoothClients açılırsa başka oyuncular için dış değerlemeye izin verilir; varsayılanı kapalıdır.

Valve’ın Source motoru (Counter-Strike, Team Fortress 2) aynı tasarımı sürdürür: varsayılan ara değerleme süresi 100 ms.

🌱 Isınma kodu: iki paket arasında konum

İki adım (G6): ters lerp const f = (t - t0) / (t1 - t0);, sonra lerp return x0 + (x1 - x0) * f;. Slayttaki örnek: (1050, 110), (1100, 130), t = 1075 → 120.
Quiz 1 · ara değerleme
Bir oyunda rakipler düz koşarken sorunsuz, ama her yön değiştirdiklerinde bir an yanlış yöne kayıp geri “çekiliyorlar”. Oyun rakipleri hangi yöntemle çiziyor ve çözüm ne?
Düzde doğru, dönüşte taşan davranış dış değerlemenin (son hızla ileri tahmin) imzasıdır. Ara değerleme yalnızca gerçek paketlerin arasını doldurur; taşma olmaz, karşılığında rakip tampon kadar geçmişte görünür. Ham gösterimde kayma değil zıplama, ara değerlemede ise ne kayma ne çekilme olurdu.
LEVEL 2
Tahmin ve uzlaştırma
kendi karakteriniz için

Problem: kendi tuşunuzu beklemek

Ara değerleme burada işe yaramaz
  • Rakibi 100 ms geçmişte görmek kabul edilebilir; fark etmezsiniz.
  • Ama kendi karakteriniz için aynı şey: tuşa bas → sunucuya git (50 ms) → sunucu işlesin → geri gel (50 ms) → tampon (100 ms). Karakter 200 ms sonra kıpırdar.
  • Bu, buzda yürümek ya da uzaktan kumandalı bir araba sürmek gibidir. Hassas hiçbir hareket yapılamaz.

Oysa istemci sonucun ne olacağını neredeyse kesin biliyor: “sağa bastım, sağa gideceğim.” Sunucu büyük olasılıkla aynısını söyleyecek.

🎮 Fikir 1: tahmin — sorma, yap
  • İstemci girdiyi sunucuya yollar ve beklemeden kendinde de uygular.
  • Bunun için hareket kodu istemcide de bulunmalı: sunucudaki fonksiyonun aynısı (aynı hız, aynı çarpışma, aynı sabit adım: G5).
  • Sunucu yine yetkili: kendi hesabını yapar ve sonucu yollar. Çoğu zaman istemcinin tahminiyle aynıdır.

Yeni sorun: sunucudan gelen haber eski. “x = 11” diyor, ama o cevap yoldayken siz iki adım daha attınız ve ekranda 13’tesiniz. Habere uyarsanız geri sıçrarsınız; uymazsanız sunucu yetkili olmaz.

Fikir 2: uzlaştırma — geri dön, kalanları yeniden oyna

  • Her girdiye artan bir sıra numarası ver. Yolladığın girdileri bir listede sakla.
  • Sunucu, durumla birlikte şunu da söyler: “en son şu numaralı girdini işledim.”
  • Haber gelince üç adım:
  • ① Sunucunun durumuna dön (o yetkili).
  • ② O numaraya kadar olan girdileri listeden at (onaylandılar).
  • ③ Listede kalanları (yoldakiler) o durumun üstüne yeniden uygula.
  • Tahmin doğruysa ③’ün sonucu ekrandakiyle aynıdır: hiçbir şey kıpırdamaz. Yanlışsa (duvara çarptınız, biri itti) ekran tek adımda doğruya gelir.
Elle izleme: her girdi +1; başlangıç x = 10
anolaysunucubekleyenekran
1istemci #1 yolladı, uyguladı10#111
2istemci #2 yolladı, uyguladı10#1 #212
3sunucu #1’i işledi; “x = 11, son #1” yolladı11#1 #212
4haber geldi: 11’e dön, #1’i at, #2’yi uygula11#212 ✓
5sunucu #2’yi işledi; “x = 12, son #2”12#212
6haber geldi: 12’ye dön, #2’yi at12—12 ✓

Uzlaştırma olmasaydı (4. satır): ekran 11’e geri atlar, 6. satırda yeniden 12’ye çıkardı. Yürürken sürekli bir adım geri çekilmek: “lastik bant” (rubber banding).

Sunucu itiraz ederse (3. satırda “x = 10, son #1”: önünüzde duvar vardı): 10’a dön, #2’yi uygula → 11. Tek bir düzeltme.

Canlı: tuşa basın, gecikmeyi hissedin 🎮

  1. Ekranı okuyun: üstte sunucunun bildiği gerçek (sarı kutu), ortada sizin ekranınız (yeşil kutu; kesikli sarı çerçeve sunucudan gelen son haber). Aradaki noktalar yoldaki mesajlar: yeşiller girdileriniz, sarılar sunucu durumu. Altta ikisinin konum–zaman grafiği.
  2. A / D ile yürüyün. Varsayılan ayarda (tahmin + uzlaştırma) yeşil kutu anında tepki verir; sarı kutu onu 150 ms geriden izler. “Geri sıçrama” sayacı 0’da kalır.
  3. “İstemci tahmini”ni kapatın: kutu 300 ms sonra ve saniyede 10 kare kıpırdar. Gecikmeyi 400’e çıkarıp bir yere tam durmayı deneyin.
  4. Tahmini açın, “uzlaştırma”yı kapatın ve D’ye basılı tutun: kutu ilerlerken saniyede 10 kez geri çekilir (kırmızı yanıp sönme); grafik testere dişi olur. Sayaç hızla artar.
  5. İkisini de açın. Yürürken “👊 sunucu itmesi”ne basın: sunucu, sizin bilmediğiniz bir nedenle (patlama, başka oyuncu) kutuyu itti. Haber gelince ekran tek seferde düzelir.
  6. “Onay bekleyen girdi” sayısına bakın: gidiş-dönüş süresi × 60. Gecikme 400 ms iken 48 girdi her haber gelişinde yeniden oynatılıyor.
🔎 DEMODA DENEYİN
  1. Ekranı okuyun: üstte sunucunun bildiği gerçek (sarı kutu), ortada sizin ekranınız (yeşil kutu; kesikli sarı çerçeve sunucudan gelen son haber). Aradaki noktalar yoldaki mesajlar: yeşiller girdileriniz, sarılar sunucu durumu. Altta ikisinin konum–zaman grafiği.
  2. A / D ile yürüyün. Varsayılan ayarda (tahmin + uzlaştırma) yeşil kutu anında tepki verir; sarı kutu onu 150 ms geriden izler. “Geri sıçrama” sayacı 0’da kalır.
  3. “İstemci tahmini”ni kapatın: kutu 300 ms sonra ve saniyede 10 kare kıpırdar. Gecikmeyi 400’e çıkarıp bir yere tam durmayı deneyin.
  4. Tahmini açın, “uzlaştırma”yı kapatın ve D’ye basılı tutun: kutu ilerlerken saniyede 10 kez geri çekilir (kırmızı yanıp sönme); grafik testere dişi olur. Sayaç hızla artar.
  5. İkisini de açın. Yürürken “👊 sunucu itmesi”ne basın: sunucu, sizin bilmediğiniz bir nedenle (patlama, başka oyuncu) kutuyu itti. Haber gelince ekran tek seferde düzelir.
  6. “Onay bekleyen girdi” sayısına bakın: gidiş-dönüş süresi × 60. Gecikme 400 ms iken 48 girdi her haber gelişinde yeniden oynatılıyor.

Kod: tahmin ve uzlaştırma (1/2)

Kod: tahmin ve uzlaştırma (2/2)

🎒 Kodu okuyalım

ifadene demekneden böyle
girdiUygula(durum, girdi)tek hareket fonksiyonuİstemci ve sunucu aynı kodu çalıştırmalı. İkisi ayrı yazılırsa küçük farklar sürekli düzeltmeye yol açar. Quake III’te bu dosyanın adı bg_pmove.c: “bg” = both games.
{ sira, yon, dt }girdi: niyet, sonuç değil“x = 105 oldum” değil, “sağa bastım”. Sunucu sonucu kendi hesaplar.
bekleyen.push(girdi)onaylanmamış girdiler listesiBir kuyruk: önden onaylananlar çıkar, arkadan yeniler girer. Boyu ≈ gidiş-dönüş süresi × girdi hızı.
ben.x = mesaj.xkoşulsuz sunucuya dön“Farklıysa dön” diye karşılaştırmak gerekmez: doğruysa yeniden oynatma aynı yere getirir.
g.sira > mesaj.sonIslenensunucunun henüz görmedikleriSıra numarası olmadan hangi girdilerin “yolda” olduğunu bilemezsiniz.
girdi.dt > 0.1 → returnsunucu girdiyi doğrulardt’yi istemci söylüyor: büyük dt yollayan hızlanır (speed hack). Gerçek sunucular istemcinin toplam süresini de denetler.
ÖnermeHareket fonksiyonu deterministikse ve sunucu girdileri istemcinin uyguladığı sırayla işliyorsa, uzlaştırma sonrası konum ekrandaki konuma eşittir (görünür düzeltme sıfır). Düzeltme yalnızca istemcinin bilemeyeceği bir şey olduğunda görünür. Maliyet: her sunucu haberinde bekleyen girdi sayısı kadar hareket adımı.

Oyunlarda: Quake III’ün tahmin döngüsü

Kaynak: id Software, Quake III Arena, cg_predict.c (GPL). İstemci tahminini yaygınlaştıran oyun QuakeWorld’dür (John Carmack, 1996).

🎮 Satır satır
  • Baştaki yorum yöntemi anlatıyor: tahmin, “sunucudan gelen en son oyuncu durumunun üstüne tahmini komutların uygulanmasıdır”; ve “her seferinde onaylanmamış bütün komutlar yeniden simüle edilir”.
  • cg.predictedPlayerState = cg.snap->ps: bizim ① adımımız.
  • Döngü, saklanan son CMD_BACKUP komutu gezer; zamanı sunucunun işlediğinden eski olanları continue ile atlar (②), kalanlar için Pmove’u çağırır (③).
  • Pmove: G5’te zıplamasını incelediğimiz hareket kodu. Sunucu ve istemci aynı dosyayı derler.
  • Son cümle: “tahmin hatalarını saptar ve sarsıntıyı azaltmak için birkaç karede eriterek gideririz.” Düzeltme bir anda değil, cg_errordecay = 100 ms içinde yumuşatılarak gösterilir (G6: yaklaşma).
  • Yorumdaki “OPTIMIZE” notu: tahmin tutmuşsa yeniden simülasyona gerek yok; ama bunun için ara durumları saklamak gerekir. Bugünkü motorlar bunu yapar.

🧪 Canlı laboratuvar: uzlaştırma

Başlangıç kodunda mavi kutu yürürken sürekli geri çekilir; “geri sıçrama” sayacı artar. Üç satır: const kalan = bekleyen.filter(g => g.seq > sonIslenen); · let x = sunucuX; for (const g of kalan) x += g.dx; · return [x, kalan];. Slayttaki örnek: (11, 1, [#1: +1, #2: +1]) → [12, [#2]].
Quiz 2 · tahmin
Çevrimiçi oyununuzda karakter koşarken yarım saniyede bir küçük bir adım geri çekiliyor. Gecikme düşük (40 ms), paket kaybı yok. En olası sebep?
Düzenli geri çekilme, tahmin ile sunucu sonucunun düzenli olarak ayrışması demektir. İki klasik neden: uzlaştırmanın eksik olması (eski duruma dönüp yoldaki girdileri unutmak) ya da hareket fonksiyonunun iki tarafta farklı çalışması (farklı zaman adımı, farklı çarpışma verisi, kayan nokta farkları). Ara değerleme tamponu yalnızca başka oyuncuların görüntüsünü etkiler.

🎬 Ara: Overwatch’ın ağ kodu, geliştiricilerinden

Developer Update | Let’s Talk Netcode | Overwatch
PlayOverwatch (Tim Ford ve Philip Orwig) · 17 dk 30 sn · YouTube’da aç

Bu sunumun üç fikrinin (ara değerleme, istemci tahmini, gecikme telafisi) büyük bir rekabetçi oyunda nasıl birlikte çalıştığı, oyunun kendi ağ programcılarının ağzından ve oyun içi görüntülerle.

🤔 İzlerkenVideoda “ben siper almıştım ama vuruldum” durumu nasıl açıklanıyor? Sunucu geçmişe kaç milisaniye geri sarabiliyor? Tahmin edilen hareket sunucuyla uyuşmayınca oyuncu ne görüyor?
LEVEL 3
Gecikme telafisi
sunucu zamanı geri sarar

Problem: gördüğünüze ateş ettiniz, orada değildi

🎮 İki numaranın faturası
  • Rakibi geçmişte görüyorsunuz: gecikme + ara değerleme tamponu.
  • Tetiğe bastınız; atış sunucuya bir gecikme süresi daha sonra varıyor.
  • Sunucu atışı aldığında rakip, sizin gördüğünüz yerden çoktan uzaklaşmış.
kalemsüre
sunucu → siz (rakibin görüntüsü)50 ms
ara değerleme tamponu100 ms
siz → sunucu (atış)50 ms
toplam200 ms

Rakip saniyede 250 birim koşuyorsa (Counter-Strike’ta bıçakla koşma hızı) 200 ms’de 50 birim yol alır. Bir oyuncunun genişliği 32 birim.

Tam üstüne nişan alan, bir buçuk gövde arkasını vurur.

İlk akla gelen: “önüne nişan alın”
  • Eski oyunlarda gerçekten böyleydi: oyuncular kendi ping’lerine göre hedefin önüne nişan almayı öğrenirdi.
  • Ne kadar öne? Ping’e bağlı; ping oynar. Her sunucuda yeniden öğrenmek gerekir.
  • Düşük ping’li oyuncu ezici üstünlük kazanır; yüksek ping’li oyuncu için oyun oynanmaz.
  • “Atışa istemci karar versin” (vurdum diyorsa vurmuştur): hile için açık davetiye.

Fikir: sunucu atışı yine kendisi sınasın; ama dünyayı nişancının tetiğe bastığında gördüğü hâle getirip öyle sınasın. Sunucu herkesin geçmişini bildiğine göre bunu yapabilir.

Çizimle: geçmişe ateş etmek

Nişancının ekranı geçmişi gösterir; sunucu atışı o geçmişe göre sınarekranında vurduysan vurmuşsundur🔫🏃🏃nişancının gördüğü yer(gecikme + tampon kadar önce)hedef “şimdi” burada(sunucudaki gerçek konum)sunucu hedefi buraya geri sarar,ışını sınar, sonra yerine koyar

Tetiğe bastığınız anda ekranınızdaki rakip, gerçekte bulunduğu yerin biraz gerisindedir. Sunucu, atış mesajı gelince herkesi sizin gördüğünüz ana geri sarar ve ışını orada sınar.

Fikir: konum kaydı tut, atışta geri sar

  • Sunucu her adımda her oyuncunun konumunu zaman damgasıyla bir kayda yazar; son ≈ 1 saniyeyi saklar.
  • Atış mesajı gelince nişancının o an gördüğü zamanı hesaplar:

görülen an = şimdi − nişancının gecikmesi − ara değerleme tamponu

  • Bütün öbür oyuncuları kayıttan o ana geri sarar (iki satır arasında lerp; satırı bulmak için ikili arama).
  • Işın testini (G3) o geçmiş konumlara karşı yapar. Vurduysa hasar şimdi verilir.
  • Herkesi yerine geri koyar.
  • Sonuç: ekranında vurduysan vurmuşsundur; ping’in ne olursa olsun tam üstüne nişan alırsın.

Formül Valve’ın belgelerindeki hâliyle: “komut yürütme zamanı = sunucu zamanı − paket gecikmesi − istemci görüntü ara değerlemesi”.

Elle izleme: sunucu saati 1.200 ms

Rakibin kaydı (20 Hz):

zaman1.0001.0501.1001.1501.200
x10011013013090
  • Atış geldi. Nişancının gecikmesi 50 ms, tampon 100 ms.
  • Görülen an = 1.200 − 50 − 100 = 1.050.
  • Kayıtta 1.050 → x = 110. Nişancı 110’a nişan almıştı: isabet.
  • Rakip şu an 90’da. Telafi olmasaydı: 20 birim fark, ıska.
  • Görülen an 1.075 olsaydı: 1.050 ile 1.100 arası, oran 0,5 → lerp(110, 130, 0,5) = 120.

Bedeli: rakip 1.150’den sonra geri dönüp 90’a, belki bir duvarın arkasına geçti. Kendi ekranında siperde; yine de vuruldu. Isınmadaki olay.

Canlı: nişan alın, ateş edin 🎯

  1. Ekranı okuyun: üst şerit sizin ekranınız (rakibin geçmişteki hâli ve kırmızı nişangâhınız); alt şerit sunucu: koyu mavi rakibin şu anki gerçek yeri, soluk mavi sizin o an gördüğünüz yer. Sağdaki koyu bölge siper. Kırmızı nokta sunucuya giden atışınız.
  2. Fareyle rakibin tam üstüne gelip tıklayın (ya da boşluk). Telafi açıkken: alt şeritte atış soluk mavinin üstüne düşer ve “İSABET” yazar; koyu mavi o sırada başka yerdedir.
  3. Telafiyi kapatın ve yine tam üstüne nişan alın: “ISKA (ekranda vurmuştu)”. Şimdi rakibin gidiş yönünde önüne nişan alarak vurmayı deneyin. Gecikmeyi 250 ms yapın: ne kadar öne?
  4. Telafiyi açın, gecikmeyi 250 ms yapın. Rakip sipere girmek üzereyken (sağ kenara yaklaşırken) üstüne ateş edin: “İSABET (rakip siperdeydi!)”. “Siperdeyken vurulan” sayacı artar.
  5. Soru: bu haksızlığı kim yaşıyor? (Vurulan.) Kimin gecikmesi yüzünden? (Vuranın.) Oyunların çoğu telafiyi bu yüzden bir üst sınırla keser.
🔎 DEMODA DENEYİN
  1. Ekranı okuyun: üst şerit sizin ekranınız (rakibin geçmişteki hâli ve kırmızı nişangâhınız); alt şerit sunucu: koyu mavi rakibin şu anki gerçek yeri, soluk mavi sizin o an gördüğünüz yer. Sağdaki koyu bölge siper. Kırmızı nokta sunucuya giden atışınız.
  2. Fareyle rakibin tam üstüne gelip tıklayın (ya da boşluk). Telafi açıkken: alt şeritte atış soluk mavinin üstüne düşer ve “İSABET” yazar; koyu mavi o sırada başka yerdedir.
  3. Telafiyi kapatın ve yine tam üstüne nişan alın: “ISKA (ekranda vurmuştu)”. Şimdi rakibin gidiş yönünde önüne nişan alarak vurmayı deneyin. Gecikmeyi 250 ms yapın: ne kadar öne?
  4. Telafiyi açın, gecikmeyi 250 ms yapın. Rakip sipere girmek üzereyken (sağ kenara yaklaşırken) üstüne ateş edin: “İSABET (rakip siperdeydi!)”. “Siperdeyken vurulan” sayacı artar.
  5. Soru: bu haksızlığı kim yaşıyor? (Vurulan.) Kimin gecikmesi yüzünden? (Vuranın.) Oyunların çoğu telafiyi bu yüzden bir üst sınırla keser.

Kod: konum kaydı ve geri sarma (1/2)

Kod: konum kaydı ve geri sarma (2/2)

🎒 Kodu okuyalım

ifadene demekneden böyle
kayit.push({ t, x, y })her sunucu adımında bir satır64 Hz × 1 sn = oyuncu başına 64 satır. Kayıt bir kuyruk: eskiler önden atılır.
KAYIT_SURESI = 1.0en çok 1 sn geriye gidilirAynı zamanda telafinin üst sınırı: 1 sn’den eski bir an istenirse en eski satır döner. Çoğu oyun sınırı çok daha düşük tutar.
geriSar(kayit, an)geçmiş bir andaki konumAra değerlemenin aynısı, bu kez sunucuda. Python sürümü satırı ikili aramayla bulur (kayıt zamana göre sıralı).
simdi - gecikme - TAMPONnişancının gördüğü anGecikmeyi sunucu kendisi ölçer (ping). İstemcinin “ben şu anı görüyordum” demesine güvenilmez; güvenilirse sahte zaman yollayan geçmişi vurur.
hedef === nisanci → continuenişancıyı geri sarmaNişancı kendi tahmin edilen (güncel) konumundan ateş etti.
hasarVersonuç şimdiki zamanda uygulanırGeçmiş değiştirilmez; yalnızca sınama geçmişte yapılır.
ÖnermeGecikme telafisiyle, nişancının ekranında isabet eden anlık (hitscan) bir atış sunucuda da isabet eder (saat tahmini hatası kadar sapmayla). Karşılığında, hedef kendi ekranındaki konumundan en çok nişancının gecikmesi + tampon kadar önceki yerinde vurulabilir. Maliyet: atış başına oyuncu sayısı × log(kayıt boyu).

Oyunlarda: kim haksızlığa uğrayacak?

🎮 Counter-Strike’tan Valorant’a
  • Half-Life / Counter-Strike (2001): Yahn Bernier’in yazısı yöntemi adıyla tanıttı (Kaynaklar). O günden beri sunucu yetkili nişancıların standardı.
  • Source motoru: geri sarma en çok 1 saniye (sv_maxunlag). Daha eski bir an istenirse 1 saniyede kesilir.
  • Overwatch: “atanın lehine” (favor the shooter) kuralı; ama nişancının ping’i belli bir eşiği aşarsa telafi kısılır ve kaçış yetenekleri (ışınlanma, kalkan) atışa karşı öncelik alır. Geliştiriciler bunu “Let’s Talk Netcode” videosunda anlatır (Videolar).
  • Valorant: 128 Hz sunucular ve oyuncuları en yakın veri merkezine bağlayan özel ağ: telafi edilecek süreyi küçültmek. “Köşeden çıkanın avantajı”nı (peeker’s advantage) azaltmanın tek gerçek yolu.
  • Mermisi yol alan silahlar (roket, ok): çoğu oyun telafi etmez; mermi sunucuda doğar ve oyuncu önüne nişan alır.
Köşeden çıkanın avantajı

Siz köşede bekliyorsunuz; rakip koşarak köşeden çıkıyor.

  • Rakip kendi ekranında anında hareket eder (tahmin) ve sizi hemen görür: siz duruyorsunuz, konumunuz güncel.
  • Siz onu geçmişte görürsünüz (ara değerleme): o sizi gördükten 100–150 ms sonra ekranınıza girer.
  • O, sizi görüp ateş etmek için sizden 100–150 ms önde başlar. İnsan tepki süresi ≈ 200 ms: avantaj ezici.

Bu yüzden rekabetçi oyunlarda beklemek yerine çıkmak ödüllendirilir; oyunun “meta”sını ağ kodu belirler.

Tasarım dersi: gecikme yok edilemez, yalnızca bir oyuncudan ötekine aktarılır. Kimin lehine aktaracağınız bir oyun tasarımı kararıdır, mühendislik kararı değil.

🐍 Laboratuvar: zamanı geri sar

Önce uçlar: if an <= kayit[0][0]: return kayit[0][1] ve if an >= kayit[-1][0]: return kayit[-1][1]. Sonra for i in range(1, len(kayit)): kayit[i][0] >= an olan ilk satırda dur; (t0, x0), (t1, x1) = kayit[i-1], kayit[i]; return x0 + (x1 - x0) * (an - t0) / (t1 - t0). Slayttaki kayıtta an = 1075 → 120; an = 1190 → 98.
Quiz 3 · gecikme telafisi
Gecikme telafisi kullanan bir oyunda 30 ms ping’li A oyuncusu sipere giriyor ve kendi ekranında güvende. 200 ms ping’li B oyuncusu, kendi ekranında A’yı hâlâ açıkta görüp vuruyor. Sunucu ne yapar ve neden?
Telafi, nişancının ekranında gördüğünü esas alır: sunucu A’yı B’nin tetiğe bastığı anda gördüğü konuma geri sarar; A o anda açıktaydı. Haksızlığı düşük ping’li A yaşar. Oyunlar bu yüzden telafiye üst sınır koyar (ör. en çok 200–250 ms): sınırın ötesinde B “önüne nişan almak” zorunda kalır.
LEVEL 4
Eşzamanlı simülasyon ve geri sarma
yalnızca girdileri yollamak

Problem: 1.500 okçu, saniyede 3 kilobayt

🎮 Durum yollamak her oyuna uymaz
  • Bir strateji oyununda 8 oyuncu, binlerce birim. Her birimin konumunu saniyede 20 kez yollamak: 1997’nin 28,8 kbit/sn’lik modemi için imkânsız, bugün bile pahalı.
  • Bir dövüş oyununda tek kare (16,7 ms) fark, bir vuruşun bloklanıp bloklanmaması demek. “100 ms geçmişteki rakip” kabul edilemez; “atanın lehine” kuralı işlemez (iki taraf da atan).

Başka bir fikir: durumu hiç yollama. Herkes oyunun tamamını kendi makinesinde simüle etsin; ağdan yalnızca girdiler geçsin (“şu birimleri seçtim, şuraya tıkladım”).

Aynı başlangıç + aynı girdiler + aynı sırada = aynı oyun. Paket boyu birim sayısından bağımsız.

Koşul: simülasyon deterministik olmalı
  • Bütün makineler bit bit aynı sonucu üretmeli. En küçük fark kelebek etkisiyle büyür: bir köylü bir adım farklı yürür, beş dakika sonra iki oyuncu farklı savaşlar izler (desync).
  • Sabit zaman adımı (G5) şart: kare süresi simülasyona giremez.
  • Rastgelelik: herkes aynı tohumla aynı üreteci, aynı sırada çağırmalı (Y15).
  • Kayan noktalı sayılar: farklı işlemci/derleyici son basamakta farklı yuvarlayabilir. Çoğu oyun tam sayı ya da sabit noktalı sayı kullanır.
  • Gezme sırası belirsiz veri yapıları (hash tablosu), başlatılmamış bellek, yerel saate bakmak: hepsi yasak.

Saptamak için: her N karede durumun sağlaması (özeti: Hash Tabloları sunumu) karşılaştırılır.

İki yöntem: bekle ya da tahmin et

① Girdi gecikmesi (delay / lockstep)
  • Bir kare, herkesin o kareye ait girdisi gelmeden simüle edilmez.
  • Girdiler beklenirken oyun donmasın diye her girdi ileriki bir kareye planlanır: “şimdi bastım, 6 kare sonra uygulansın.” O zamana kadar karşıya da varmış olur.
  • Tutarlılık kusursuz. Bedeli: kendi tuşunuz de gecikir.
  • Strateji oyununda 200–500 ms’lik komut gecikmesi fark edilmez (birim “Emredersiniz!” der, sonra yürür). Dövüş oyununda 6 kare felakettir.
kare100101102…106
yerel tuşyumruk
uygulanan———yumruk
② Geri sarma (rollback)
  • Yerel girdi hemen uygulanır.
  • Rakibin henüz gelmemiş girdisi tahmin edilir: “bir önceki karede ne yapıyorsa onu yapıyor.” (İnsanlar saniyede birkaç kez tuş değiştirir; 60 karenin çoğunda tahmin tutar.)
  • Gerçek girdi gelince tahminle karşılaştırılır. Tutmuşsa: hiçbir şey olmaz.
  • Tutmamışsa: oyun o kareye geri sarılır (saklanan durum yüklenir), doğru girdiyle bugüne kadar olan kareler tek bir çizim karesi içinde yeniden simüle edilir.
  • Görünen: rakip birkaç piksel “düzelir”. Hissedilen: gecikmesiz kontrol.
kare100101102103
rakip (tahmin)ileriileriileriileri
rakip (gerçek)ilerizıpla??

103’te, 101’in gerçeği geldi: 101’e dön, “zıpla” ile 101, 102, 103’ü yeniden oyna.

Canlı: aynı gecikme, iki yöntem 🥊

  1. Ekranı okuyun: yeşil siz (A / D), kırmızı uzaktaki rakip. Soluk gölgeler gecikmesiz bir dünyada görecekleriniz. Altta son 60 karenin şeridi ve sayaçlar.
  2. Geri sarma (başlangıç), gecikme 6 kare: A / D’ye basın; yeşil anında kıpırdar ve gölgesiyle üst üstedir. Kırmızı rakip çoğu zaman gölgesinin biraz gerisindedir; yön değiştirdiğinde sarı yanıp sönerek düzelir (şeritte kırmızı kare).
  3. Sayaçlar: “geri sarma” yalnızca rakip tuş değiştirdiğinde artar; saniyede 60 değil, birkaç kez.
  4. Yöntemi girdi gecikmesi yapın: şerit gri. A / D’ye basıp bırakın: karakteriniz 100 ms sonra başlar, 100 ms sonra durur. Gölgenizle aranız açılır.
  5. Gecikmeyi 12 kareye (200 ms) çıkarın. Girdi gecikmesinde oyun çamurda yürümek gibi; geri sarmada kendi hareketiniz hâlâ anında, ama rakibin düzeltmeleri büyür (“en büyük düzeltme” sayacı).
  6. Gecikmeyi 0 yapın: iki yöntem aynı. Fark, gecikmenin faturasını kimin ödediğinde: girdi gecikmesinde eliniz, geri sarmada gözünüz.
🔎 DEMODA DENEYİN
  1. Ekranı okuyun: yeşil siz (A / D), kırmızı uzaktaki rakip. Soluk gölgeler gecikmesiz bir dünyada görecekleriniz. Altta son 60 karenin şeridi ve sayaçlar.
  2. Geri sarma (başlangıç), gecikme 6 kare: A / D’ye basın; yeşil anında kıpırdar ve gölgesiyle üst üstedir. Kırmızı rakip çoğu zaman gölgesinin biraz gerisindedir; yön değiştirdiğinde sarı yanıp sönerek düzelir (şeritte kırmızı kare).
  3. Sayaçlar: “geri sarma” yalnızca rakip tuş değiştirdiğinde artar; saniyede 60 değil, birkaç kez.
  4. Yöntemi girdi gecikmesi yapın: şerit gri. A / D’ye basıp bırakın: karakteriniz 100 ms sonra başlar, 100 ms sonra durur. Gölgenizle aranız açılır.
  5. Gecikmeyi 12 kareye (200 ms) çıkarın. Girdi gecikmesinde oyun çamurda yürümek gibi; geri sarmada kendi hareketiniz hâlâ anında, ama rakibin düzeltmeleri büyür (“en büyük düzeltme” sayacı).
  6. Gecikmeyi 0 yapın: iki yöntem aynı. Fark, gecikmenin faturasını kimin ödediğinde: girdi gecikmesinde eliniz, geri sarmada gözünüz.

Kod: geri sarma döngüsü ve eşzamansızlık denetimi (1/2)

Kod: geri sarma döngüsü ve eşzamansızlık denetimi (2/2)

🎒 Kodu okuyalım

ifadene demekneden böyle
adim(durum, girdi1, girdi2)saf fonksiyon: çizim yok, saat yok, rastgelelik tohumluGeri sarmada aynı kare birçok kez simüle edilir; yan etkisi (ses, parçacık) olan kod her seferinde yeniden tetiklenir. Simülasyon ile sunum ayrılmalı.
kesin, kesinKareiki tarafın girdisi de bilinen son durumGeri sarılacak yer. Bundan eskisini saklamaya gerek yok.
while (uzak[kesinKare] !== undefined)yeni gelen gerçek girdileri işleKesin durum yalnızca gerçek girdilerle ilerler; asla tahminle değil.
varsayim = uzak[kesinKare - 1]tahmin: son bilinen girdiyi tekrarlaBasit ama etkili: tutulan bir tuş birçok kare sürer.
for (k = kesinKare .. kare)her kare, kesin durumdan bugüne yeniden simüleEn basit yazım: gecikme L kare ise karede L + 1 adım. Dövüş oyununda durum küçük olduğu için ucuz. GGPO yalnızca tahmin tutmayınca yeniden simüle eder.
Python: first_desyncsağlama listelerinde ilk farkHata ayıklamanın başlangıcı: ayrışmanın hangi karede başladığını bulmak. Sonra o karenin durum dökümleri karşılaştırılır.
ÖnermeGeri sarmada yerel girdi gecikmesi sıfırdır; ağ gecikmesi L kare ise bir yanlış tahmin en çok L kare geriye sarmayı ve L karenin yeniden simülasyonunu gerektirir. Girdi gecikmesinde yeniden simülasyon yoktur; yerel gecikme L karedir. İkisi de simülasyon deterministik olduğu sürece doğrudur.

Oyunlarda: Age of Empires ve dövüş oyunları

🎮 “28,8’lik modemde 1.500 okçu” (1997)
  • Age of Empires’ın programcıları Paul Bettner ve Mark Terrano’nun yazısı (Kaynaklar) eşzamanlı simülasyonun klasik anlatımıdır.
  • Hedef: 8 oyuncu, 16 MB bellekli Pentium 90, 28,8 kbit/sn modem.
  • Çözüm: her makine aynı simülasyonu koşturur; ağdan yalnızca komutlar geçer. Komutlar iki tur sonrası için planlanır; bir tur tipik olarak 200 ms.
  • Yazıdaki ölçüm: strateji oyununda 250 ms’lik komut gecikmesi fark edilmiyor; 250–500 ms rahat oynanıyor.
  • En zor kısım: eşzamansızlık avı. Yazıdaki örnek: haritadaki bir geyik iki makinede biraz farklı otluyor; dakikalar sonra bir köylü mızrağını ıskalıyor ve yiyecek sayıları ayrışıyor.
  • Yan fayda: herkes aynı simülasyonu koştuğu için kaynak hilesi yapan makine “eşzamansız” diye işaretlenir. (Harita hilesi ise mümkün: bütün dünya her makinede var.)
GGPO ve dövüş oyunlarının dönüşümü
  • 2006: Tony Cannon (EVO turnuvasının kurucularından) Street Fighter II’nin çevrimiçi sürümündeki girdi gecikmesinden bıkıp GGPO’yu (“Good Game, Peace Out”) yazdı: genel amaçlı geri sarma kütüphanesi.
  • Oyundan üç şey ister: durumu kaydet, durumu yükle, verilen girdilerle bir kare ilerle.
  • 2019: kaynak kodu MIT lisansıyla açıldı.
  • 2020: pandemide turnuvalar çevrimiçine taşınınca “geri sarma” oyuncuların talep ettiği bir özellik oldu. Street Fighter 6, Guilty Gear Strive, Tekken 8, Mortal Kombat hep geri sarma kullanır.
  • Mortal Kombat ekibinin GDC konuşmasının adı durumu özetler: “16 milisaniyede 8 kare” (Videolar). Bir çizim karesi içinde 8 kareyi yeniden simüle edebilmek için motoru baştan düzenlediler.
  • Rocket League (araç fiziği) ve bazı nişancılar da aynı fikri kullanır: tahmin et, tutmazsa geri sar ve yeniden simüle et.

🌱 Isınma kodu: kaç kare geride?

Bir kare 1000 / fps milisaniye sürer. return Math.ceil(gecikmeMs * fps / 1000);. Örnek: 100 ms, 60 FPS → 6 kare; 17 ms → 2 kare (16,7’yi az da olsa aştı); 0 ms → 0.

🐍 Laboratuvar: eşzamansızlık nerede başladı?

for i in range(min(len(a), len(b))): içinde if a[i] != b[i]: return i; döngü biterse return -1. Listelerden biri daha kısa olabilir (bir makine geride): yalnızca ortak kısım karşılaştırılır.
Quiz 4 · geri sarma
İki oyunculu bir dövüş oyununda ağ gecikmesi 5 kare. Geri sarma yöntemiyle, rakip 3 saniye boyunca hiç tuş değiştirmeden ileri yürüyor. Bu sürede kaç kez geri sarma (görünür düzeltme) olur?
Tahmin, rakibin son bilinen girdisini tekrarlamaktır. Rakip girdisini değiştirmedikçe tahmin doğrudur ve gelen gerçek girdi yalnızca onaylar. Geri sarma, girdi değiştiği karelerde olur (yürümeyi bıraktığında bir kez). Bu yüzden yöntem pratikte ucuzdur: oyuncular saniyede 60 değil, birkaç kez tuş değiştirir.
LEVEL 5
Hepsi bir arada
tam kod, araçlar, kapanış

Hangi oyun hangi yöntemi kullanır?

türyöntemağdan geçengecikmenin faturasıörnek
Nişancı (FPS, battle royale)yetkili sunucu + ara değerleme + tahmin + gecikme telafisigirdiler ↑, anlık görüntüler ↓vurulan oyuncu (“siperdeydim”)Quake III, Counter-Strike, Valorant, Fortnite
Strateji (RTS)eşzamanlı simülasyon + komut gecikmesiyalnızca komutlarherkesin komutu 200+ ms geçAge of Empires, StarCraft, Factorio
Dövüşeşzamanlı simülasyon + geri sarmayalnızca girdilerrakibin görüntüsünde küçük düzeltmelerStreet Fighter 6, Guilty Gear Strive
Fizik ağırlıklıyetkili sunucu + istemcide tahmin ve geri sarmalı yeniden simülasyongirdiler ↑, durum ↓top / araç ara sıra “düzelir”Rocket League
MMO, sıra tabanlı, kartyetkili sunucu; tahmin az ya da yokistek ↑, sonuç ↓tıklamadan sonra kısa beklemeWorld of Warcraft, Hearthstone
Eşli macera (co-op)çoğu zaman bir oyuncu “ev sahibi” sunucugirdiler ↑, durum ↓ev sahibi olmayanlarStardew Valley, Minecraft (yerel ağ)

Ortak kural: istemciye asla güvenme. İstemci niyetini söyler; sonucu sunucu (ya da deterministik simülasyon) belirler. Ağ koduna başlamadan önce sorulacak soru: bu oyunda hangi gecikme, kimin tarafından, ne kadar hoş görülebilir?

📦 Tam kod: p5.js ile tek sayfada sahte ağ

İskelet. “Ağ”, mesajları GECIKME kadar bekleten iki diziden ibaret.

🎮 Çalıştırın, kopyalayın, bozun

▶ Eksiksiz örneği aç — yaklaşık 80 satır: A / D yürü; + / − gecikme; 1 / 2 / 3 tahmin, uzlaştırma, ara değerleme.

Deneyin:

  • gonder’e %10 paket kaybı ve rastgele gecikme (jitter) ekleyin.
  • Sunucuya bir duvar koyun (istemci bilmesin): tahmin hatası ve düzeltmeyi izleyin.
  • Düzeltmeyi bir anda değil, G6’daki yaklas ile 100 ms’de eriterek gösterin (Quake III).
  • Boşlukla ateş: önce telafisiz, sonra konum kaydı ve geri sarmayla.
  • İki tarayıcı sekmesi arasında gerçek mesajlaşma: BroadcastChannel; sonra WebSocket.

🧰 Motorlarda ve kütüphanelerde

UnityNetcode for GameObjects · Mirror · Fish-Networking · Photon Fusion / QuantumNGO ve Mirror: yetkili sunucu, ara değerleme hazır. Fish-Networking ve Fusion: tahmin + uzlaştırma hazır. Fusion: gecikme telafisi hazır. Quantum: deterministik geri sarma.
UnrealReplication · Character Movement Component · Network PredictionKarakter hareketinde tahmin ve uzlaştırma motorun içinde gelir; Fortnite ve Valorant bunun üstüne kurulu.
GodotMultiplayerSpawner / MultiplayerSynchronizer · netfox · Rollback Netcode eklentisiÇekirdek: durum eşleme. Tahmin, ara değerleme ve geri sarma için eklentiler.
TarayıcıWebSocket · WebRTC (veri kanalı) · Colyseus · geckos.ioWebSocket TCP üzerindedir (kayıp paket herkesi bekletir); gerçek zamanlı oyunda WebRTC’nin güvenilmez veri kanalı tercih edilir.
KütüphanelerGGPO · yojimbo / netcode (Glenn Fiedler) · ENet · Steam NetworkingGGPO: geri sarma. Diğerleri: UDP üzerinde bağlantı, güvenilirlik, şifreleme.
SınamaClumsy (Windows) · Network Link Conditioner (macOS) · tc netem (Linux)Yapay gecikme, kayıp ve dalgalanma. Ağ kodu yalnızca yerel ağda denenirse hiçbir sorunu göstermez.

Pratik kural: ilk çevrimiçi oyununuzda motorun ya da kütüphanenin hazır bileşenlerini kullanın; ama ne yaptıklarını bilmeden ayarlarını (tampon, adım hızı, telafi sınırı) seçemezsiniz. Ve ilk günden 150 ms yapay gecikmeyle deneyin.

🤖 Bu konuyu bir YZ kod asistanıyla yazarken

Sık yapılan hatalar — kontrol edin
  • İstemcinin konum yollaması (girdi yerine): hileye açık
  • Sunucunun girdiyi doğrulamaması (dt, hız, bekleme süresi)
  • Tahmin var, uzlaştırma yok: lastik bant
  • İstemci ve sunucuda farklı hareket kodu ya da farklı zaman adımı
  • Rakipleri dış değerlemeyle çizmek
  • Tampon < bir paket aralığı: sürekli boşalan tampon
  • Gecikme telafisinde istemcinin bildirdiği zamana güvenmek; üst sınır koymamak
  • Geri sarmalı oyunda simülasyonun içinde Math.random(), deltaTime, ses/parçacık tetiklemek
İyi bir istek (prompt) örneği

“p5.js, gerçek ağ yok: sunucu ve istemci aynı sayfada; aralarındaki mesajlar GECIKME saniye bekleten iki kuyrukla taşınsın (ayarlanabilir kayıp ve dalgalanma). (1) Yetkili sunucu: yalnızca numaralı girdiler kabul edilsin, 20 Hz sabit adım, her adımda durum + son işlenen girdi numarası yayınlansın. (2) İstemci tahmini ve uzlaştırma: hareket fonksiyonu iki tarafta ortak. (3) Öbür varlıklar için 100 ms tamponlu ara değerleme. (4) Hata ayıklama görünümü: sunucudaki gerçek konumları yarı saydam çiz; ekrana bekleyen girdi sayısını ve düzeltme büyüklüğünü yaz. (5) Test: gecikme 0, 100 ve 300 ms’de, kayıp %0 ve %10’da düz yürürken düzeltme sıfır olmalı.”

  • “Çok oyunculu yap” demeyin; hangi modeli istediğinizi söyleyin.
  • Sahte ağla başlatın: gerçek soketler hatayı ayıklamayı on kat zorlaştırır.
  • Gecikmeli test isteyin; asistanın yazdığı kod yerel ağda hep çalışır.

💬 Tartışma soruları

Yanınızdakiyle 2 dakika konuşun, sonra sınıfça tartışalım
  1. Oynadığınız çevrimiçi oyunlarda yaşadığınız “haksızlıkları” sayın (vurdum saymadı, siperde öldüm, geri çekildim, rakip ışınlandı). Her biri bu sunumdaki hangi tekniğin yan etkisi?
  2. Gecikme telafisinde haksızlığı vurulan yaşar; telafi olmazsa vuran yaşar. Siz tasarımcı olsanız kimin lehine karar verirdiniz? Rekabetçi bir oyun ile eğlencelik bir oyun için cevabınız değişir mi?
  3. Bir strateji oyununda harita hilesi (savaş sisini kaldırmak, G4) eşzamanlı simülasyonda neden önlenemez? Yetkili sunucuda nasıl önlenir? Bedeli ne?
  4. Dövüş oyunu toplulukları geri sarmayı bir “özellik” olarak talep etti ve yapımcıları buna zorladı. Oyuncuların bir ağ algoritmasını adıyla bilip istemesi sizce neden bu türde oldu?
  5. Yüksek sunucu adım hızı (128 Hz) pahalıdır. Hangi oyun türlerinde bu paraya değer, hangilerinde 20 Hz yeter?

🚀 Proje ve araştırma önerileri

  1. [kolay] Gecikme hissi deneyi — Örnek kodu arkadaşlarınıza oynatın: tahmin kapalıyken hangi gecikmeden sonra “oynanmaz” diyorlar? Açıkken? Sonuçları tabloya dökün.
  2. [kolay] Seyirci modu — Bir sekmede oynanan basit bir oyunu BroadcastChannel ile ikinci sekmeye yayınlayın; ikinci sekme 10 Hz paketlerle ara değerleme yaparak göstersin.
  3. [orta] İki sekmeli hava hokeyi — Yetkili “sunucu” sekmesi + iki oyuncu sekmesi; raketler için tahmin + uzlaştırma, top için ara değerleme. Yapay gecikme sürgüsü.
  4. [orta] Geri sarmalı Pong — Tam sayılı, sabit adımlı deterministik Pong; iki sekme yalnızca girdileri paylaşsın; durum kaydet / yükle / ilerle ile geri sarma. Her 60 karede sağlama karşılaştırın.
  5. [zor] Nişancı prototipi — Yukarıdan bakışlı iki oyunculu nişancı: gecikme telafili anlık atış, telafi sınırı ayarı, “siperdeyken vurulma” sayacı. Hangi sınır adil hissettiriyor?
  6. [araştırma] Oyun ağ kodu incelemesi — Açık kaynak bir oyunun (Quake III, OpenArena, Xonotic, Teeworlds, Veloren) ağ kodunda anlık görüntü, tahmin ve telafi fonksiyonlarını bulun; hangi ayarları kullanıyor?

Neler öğrendik? 🎒

  • Gecikme yok edilemez; saklanır ve bir oyuncudan ötekine aktarılır. Kimin ödeyeceği tasarım kararıdır.
  • Yetkili sunucu: istemci girdi yollar, sunucu karar verir. İstemciye güvenme.
  • Ara değerleme: başka oyuncuları ≈ iki paket aralığı kadar geçmişte, gerçek paketlerin arasını lerp ederek çiz. Dış değerleme dönüşlerde taşar.
  • Tahmin + uzlaştırma: kendi girdini hemen uygula; sunucu haberi gelince ona dön ve onaylanmamış girdileri yeniden oynat. (Quake III.)
  • Gecikme telafisi: sunucu, atışı sınarken hedefleri nişancının gördüğü ana geri sarar. Bedeli: siperdeyken vurulmak; çare: üst sınır.
  • Eşzamanlı simülasyon: yalnızca girdiler paylaşılır; determinizm şart. Girdi gecikmesi (strateji) ya da geri sarma (dövüş).
  • Sonraki: geri izleme: “otomatik düzenle” tuşu çantayı nasıl dolduruyor, bir bulmacanın çözülebilir olduğunu bilgisayar nasıl anlıyor?

🎮 Bu sunumdaki oyunlar (1/2)

Adı geçen oyunu tanımıyorsanız: oyuncuların kullandığı wiki sayfası ve oyunu kısaca gösteren bir video. ▶ video slaydın üstünde açılır (Esc ile kapanır); 🎯 işaretli bağlantılar doğrudan bu sunumdaki konuya gider.

Quake 1996 · nişancı
Tamamen 3B ilk büyük nişancılardan; BSP ve görünürlük tablolarının ders kitabı örneği.
Quake III Arena 1999 · arena nişancı
Hızlı tempolu çok oyunculu nişancı; kaynak kodu açık.
VALORANT 2020 · taktik nişancı
Riot’un 5’e 5 taktik nişancısı; 128 tick sunucular ve “savaş sisi” ile hile önleme.
Counter-Strike 1999– · takım nişancısı
Teröristler ve antiterör timi; isabet ve gecikme telafisi çok tartışılan rekabetçi nişancı.
Overwatch 2016– · takım nişancısı
Kahraman tabanlı takım nişancısı; ağ kodu ve isabet kaydı GDC’de ayrıntılı anlatıldı.
Half-Life 1998 · nişancı
Hikâyeyi ara sahnesiz anlatan, türü değiştiren nişancı; asker timleri birlikte hareket eder.
Age of Empires 1997 · gerçek zamanlı strateji
Tarihî uygarlıklarla ordu kurup savaşılan klasik strateji serisi.
Street Fighter 1987– · dövüş
Dövüş oyunlarının klasiği (en ünlüsü 1991’deki II); vuruş kutuları ve kare kare zamanlama türün temeli.

🎮 Bu sunumdaki oyunlar (2/2)

Adı geçen oyunu tanımıyorsanız: oyuncuların kullandığı wiki sayfası ve oyunu kısaca gösteren bir video. ▶ video slaydın üstünde açılır (Esc ile kapanır); 🎯 işaretli bağlantılar doğrudan bu sunumdaki konuya gider.

Rocket League 2015 · araba futbolu
Roket destekli arabalarla oynanan futbol; fiziği ve ağ kodu çok incelendi.
Fortnite 2017 · battle royale
100 kişilik hayatta kalma oyunu; 2025’te oyuncuların sesle konuşabildiği YZ Darth Vader eklendi.
StarCraft 1998 / 2010 · gerçek zamanlı strateji
Üç ırkın savaştığı, e-sporu başlatan strateji serisi; StarCraft II, DeepMind’ın AlphaStar’ının sahnesi oldu.
Factorio 2020 · fabrika kurma
Uzaylı bir gezegende giderek büyüyen otomatik fabrikalar kurarsınız; yerli böcekler saldırır.
World of Warcraft 2004 · MMO
Milyonlarca oyunculu çevrimiçi rol yapma oyunu.
Hearthstone 2014 · dijital kart oyunu
Warcraft evreninde sıra tabanlı kart oyunu; rakibin elini göremezsiniz.
Stardew Valley 2016 · çiftlik simülasyonu
Dedenizden kalan çiftliği canlandırdığınız, tek kişinin yaptığı ünlü bağımsız oyun.
Minecraft 2011 · kum havuzu
Bloklardan oluşan, prosedürel üretilen, neredeyse sonsuz bir dünya.

Kaynaklar: videolar 🎬

Kaynaklar: kaynak kodu, yazılar ve etkileşimli sayfalar 📚

NEXT LEVEL ▶
Geri İzleme: Envanter ve Bulmacalar
seç, dene, geri al: çanta düzenleme, sudoku, Sokoban
SONRAKİ SUNUM ▶