O'yinlar

56k modemda MMORPG: 2004-yildagi RuneScape qanday 5 KB/s ga sig'ib qolgan edi

1-sentabr, 2026, 07:147 ko'rish7 daqiqa o'qish
56k modemda MMORPG: 2004-yildagi RuneScape qanday 5 KB/s ga sig'ib qolgan edi

2004-yilda ana telefon tarmog'i orqali internetga ulanib, RuneScape o'ynagan har qanday bolaning xotirasida ota-onasining telefonni ko'tarishi bilan ulanishning to'xtashi yaqiniy yaqin bo'lgan travma sifatida qolgan. Lekin bu travma orqa planda yurgan muhandislik dahosi yashirin edi: Java applet shaklida brauzerda ishlaydigan, serverda 2000 tagacha o'yinchi bo'ladigan, ekranda bir vaqtning o'zida yuzlab personajlar harakatlanadigan 3D MMORPG — 56 kilobitlik modemning haqiqiy 5 KB/s tezligida.

Nega bu bugun ham muhim

Zamonaviy o'yinlar gigabaytlik yuklamalar, UDP protokollari va CDN tarmoqlari bilan ishlaydi. RuneScape 2004 esa bitta TCP soketi, ISAAC shifrlash va har baytni hisoblab chiqadigan protokol ustida qurilgan edi. Bu — cheklovlar ostida samarali arxitektura qanday qurilishi haqidagi dars, u bugungi kunda ham mikroxizmatlar, IoT va past tarmoq kanallari uchun actual.

this.outboundCipher = new ISAAC(seed);

for (int index = 0; index < 4; index++) {
    seed[index] += 50;
}

this.inboundCipher = new ISAAC(seed);

Uchta qattiq cheklov

Jagex komanda uchta fizikal haqiqatga duch kelgan edi:

  • Kanal: 56 kbps downstream (haqiqida ~5 KB/s), upstream esa undan ham past. Protokol sarlavhalari va shovqin hisobga olinsa, o'yin uchun qoladigan hajm minimaldi.
  • Muhit: Java applet, brauzer sandboksida. Hech qanday raw socket, hech qanday UDP — faqat bitta TCP ulanishi, tartibga solingan segmentlar va har bir segment uchun qo'shimcha sarlavha xarajatlari.
  • Server sikli: 600 millisekundlik diskret tiklar. Har tikda server har bir o'yinchi uchun ko'rinadigan barcha o'zgarishlarni hisoblab, keyingi tikka yetkazib yuborishi kerak edi.

Bu cheklovlar o'zaro o'zlarini kuchaytirardi: TCP ning Nagle algoritmi kichik paketlarni to'plash uchun kechiktirishi, 600 ms tik o'z navbatida paketlarni jamlash va yuborish uchun juda qisqa vaqt qoldirishi.

public void putOpcode(int opcode) {
    this.putByte(opcode + this.outboundCipher.value());
}

Faqat opcode shifrlanadi — nima uchun?

Kirish handshake tugagach, Jagex ISAAC oqim shifrini o'rnatadi. Diqqat: paket tanasi (body) shifrlanmaydi, faqat boshidagi opcode — bitta baytlik buyruq kodi. Ushbu opcode paketning qanday tahlil qilinishi va unda qayerda tugashi haqida ma'lumot beradi. Agar uchinchi shaxs opcode ni o'qiy olmasa, qolgan baytlar aniq tuzilmagan baytlar to'plamidan iborat qoladi.

Shifrlash quyidagicha ishlaydi: mijoz va server o'rtasida 4 ta butun sonli kalit bo'linadi. Mijoz o'ziga xos 2 tasini generatsiya qiladi, 2 tasi serverdan keladi. Har bir yo'nalish uchun alohida ISAAC oqimi: mijozdan serverga — outboundCipher, serverdan mijozga — inboundCipher (kalit so'zlariga 50 qo'shilgan holda). Opcode yuborilganda:

this.currentOpcode = (this.currentOpcode - this.inboundCipher.value()) & 0xFF;
putByte(opcode + outboundCipher.value())

Qabul qilinayotganda teskari amal bajariladi. Bu — eng arzon himoya: faqat bitta bayt shifrlanadi, lekin protokol tuzilishi yashirin qoladi.

Bitta qadam yurish paketi anatomiyasi

Foydalanuvchi ekranda bir tomonga klik qiladi. Mijoz avval mahalliy to'siq qaramida (collision map) kenglikka birinchi qidiruv (BFS) bilan yo'l topib, keyin paketni tuzadi. Paket tarkibi:

this.outboundStream.putOpcode(ClientToServerOpcodes.WALK_TILE);
this.outboundStream.putByte(4 + 2 * (pathLength - 1) + 1);
  • OpcodeWALK_TILE (shifrlangan holda).
  • Uzunlik bayti — paket tanasining hajmi (o'zgaruvchi bo'lgani uchun).
  • Boshlang'ich nuqta — 4 bayt (X va Z koordinatalar uchun short — 2 baytlik butun son).
  • Har bir keyingi nuqta (waypoint) — 2 bayt (X va Z uchun delta, ya'ni farq; har biri signed byte, diapazon -128..127).
  • Ctrl tugmasi belgisi — 1 bayt (yugurish/piyoda rejimini o'zgartiradi).

Formula: 4 + 2 * (pathLength - 1) + 1 bayt. Agar yo'l 10 qadam bo'lsa, paket tanasi 23 baytdan iborat. Absolyut koordinatalar yuborilsa (har qadamda 4 bayt), bu 40 bayt bo'lardi — 50% tejash.

Delta yondashuvi — bu Jagex ning "bayt qimmat" falsafasining birinchi namunasidan tashqari emas. Har bir qo'shimcha waypoint 2 bayt orttiradi, 4 bayt emas. Uzoq masofaga yurish paketlari hammasi ham kichik, lekin uzoq yo'l har doim kam uchraydi.

int firstX = pathX[0];
int firstZ = pathZ[0];

this.outboundStream.putShort(this.playerPositionX + firstX);
this.outboundStream.putShort(this.playerPositionZ + firstZ);

for (int i = 1; i < pathLength; i++) {
    this.outboundStream.putByte(this.pathX[i] - firstX);
    this.outboundStream.putByte(this.pathZ[i] - firstZ);
}

Mijoz tomoni yo'l topish — serverni yuklamaslik

Yo'l topish (pathfinding) serverda emas, mijozda amalga oshiriladi. Bu juda ahamiyati bor: server faqat natijani (waypointlar ketma-ketligini) qabul qiladi va tekshiradi. Agar har bir o'yinchi uchun server BFS ishlasa, 2000 o'yinchi uchun 600 ms ichida bu mahz kompyutatsiya to'qnashuvi yaratardi. Mijozga yukni o'tkazish — bu optimistik UI ning erta misollaridan biri: foydalanuvchi darhol harakatni ko'radi, server esa keyinroq tasdiqlaydi yoki bekor qiladi.

TCP va 600 ms tik o'rtasidagi jangi

Bitta TCP ulanishi degani — barcha paketlar (harakat, so'ngi chat, interfeys yangilanishlari) bitta navbatda turadi. Agar katta paket (masalan, yangi hudud yuklanishi) kanalni to'ldirsa, harakat paketi kutib turishi shart. Jagex bunga uchta yechim bilan duch kelgan:

this.outboundStream.putByte(this.keyStatus[Keys.CTRL] == 1 ? 1 : 0);
  1. Paketlarni siniflash: harakat va battle paketlari yuqori ustuvorlik, hudud yuklanishi past.
  2. Nagle'ni o'chirish (TCP_NODELAY): kichik paketlar darhol yuborilsin, to'planmasin.
  3. Protokol mamlakati: har qanday ma'lumot faqat shart bo'lganda yuboriladi. Masalan, o'yinchi ko'rmagan ob'ektlar haqida ma'lumot yuborilmaydi — interest management (qiziqni boshqarish) server tikida amalga oshiriladi.

600 ms tik — bu emas, 16.6 ms (60 FPS) emas. Bu juda katta oynadir, lekin TCP ning tartibga solish ta'minoti va kanal cheklovlari bilan mos keladi. Server har tikda "nima o'zgardi?" savoliga javob beradi va faqat o'zgarishlarni (delta) yuboradi.

Taqqoslash: zamonaviy yechimlar bilan farqi

Bugungi o'yinlar (masalan, World of Warcraft yoki Valorant) UDP asosida, maxsus kongruent protokollar (QUIC, ENet, Reliable UDP) va klient-ta'minot serverlari (CDN) ishlatadi. Ular paket yo'qotilishini o'zlari boshqaradi, TCP ning head-of-line blocking muammosi yo'q. Lekin 2004-yilda brauzer sandboksida UDP yo'q edi. Jagex TCP ning cheklovlari ichida eng samarali protokolni qurdi.

int opcode = player.inboundStream.takeOpcode();

if (opcode == ClientToServerOpcodes.WALK_TILE) {
    int length = player.inboundStream.takeByte();

    int deltaCount = (length - 4 - 1) / 2;

    int[] firstWaypoint = new int[2];
    firstWaypoint[0] = player.inboundStream.takeShort();
    firstWaypoint[1] = player.inboundStream.takeShort();

    int[][] waypointDeltas = new int[deltaCount][2];
    for (int i = 0; i < deltaCount; i++) {
        waypointDeltas[i][0] = player.inboundStream.takeByte();
        waypointDeltas[i][1] = player.inboundStream.takeByte();
    }

    boolean holdingCtrl = player.inboundStream.takeByte() == 1;

    player.processWalkTile(firstWaypoint, waypointDeltas, holdingCtrl);
}

Yaqinlashuv: Old School RuneScape bugungi kunda ham shu arxitekturaning modernlashtirilgan versiyasini ishlatadi. Opcode shifrlash, delta kodlash, 600 ms tik — hammasi saqlanib qolgan. Bu — arxitektura uzluksizligining ko'rinishi.

Ochiq savollar va cheklovlar

  • Paket yo'qolishi: TCP garantiya beradi, lekin kechiktiradi. Agar 600 ms tikka yetib bormasa, o'yinchi "lag" his qiladi. Jagex bunga qanday yuz tutgani (retransmission va client-side prediction) decompiled kodda to'liq ko'rinmaydi.
  • Natija e'tibori (determinism): Server va mijoz harakat natijasini bir xil hisoblashi kerak. Floating-point nuqtai o'tkazib yuborish xatolari sinxronizatsiya buzilishiga olib kelishi mumkin. RuneScape butun sonli (integer) koordinatalar ishlatgan — bu muammoni oldini oldi.
  • Kengaytirish: 2004 serverlari bitta core-da ishlaydi (single-threaded). 2000 o'yinchi — bu chetki. Zamonaviy OSRS sharding va masofaviy serverlar orqali bu cheklovni hal qilmoqda, lekin protokol o'zgaribsiz qolgan.

Xulosa: bayt iqtisodchiligi handasasi

RuneScape 2004 — bu "qanday qilib" degan savolga javob bermay, "nima uchun shunday" degan savolga javob beruvchi misol. Har bir bayt uchun kurashish, protokol darajasida xavfsizlik (opcode shifrlash), mijozga yukni o'tkazish, server tikini muvofiqlashtirish — bularning har biri bugungi dasturlashda ham actual. Agar siz mikroxizmatlar, real-time tizimlar yoki past tarmoq kanallari (LoRa, NB-IoT, satellite) ustida ishlaysangiz, 2004-yildagi Jagex muhandislaridan o'rganish ko'p narsa bor.

if (opcode == ClientToServerOpcodes.USE_ITEM_ON_ITEM) {
    int sourceItemId = player.inboundStream.takeShort();
    int sourceInterfaceId = player.inboundStream.takeShort();
    int sourceInterfaceSlot = player.inboundStream.takeShort();

    int targetItemId = player.inboundStream.takeShort();
    int targetInterfaceId = player.inboundStream.takeShort();
    int targetInterfaceSlot = player.inboundStream.takeShort();

    player.processUseItemOnItem(/* ... */);
}

Keyingi safar "lag" deb la'natlab yuborganingizda, esingizga tushsin: 20 yil avval 5 KB/s da 3D dunyo, 2000 o'yinchi va real-time janglar ishlatdi. Ular buni bayt bayt hisoblab, har birini qadrlab qilganlari uchun amalga oshirib bo'ldilar.

Asl manba: jkm.dev

Manba: Hacker News
#runescape #networking #game-dev #tcp #optimization
Telegram da muhokama qilish