Ashenfall SF | Devlog

[Devlog] Birleşik İkili Harita Formatı (.ashenfallmap): 1.5 Milyon Satır Çöpün Temizliği, Sıfır Gecikmeli Bellek Eşleme ve Deterministik Arazi Hattı​

Ashenfall motorunun dosya sistemi ve arazi hattında, 2004 yılından miras kalan ilkel ve parçalanmış map yükleme/yazma mimarisini tamamen çöpe attım. Klasörler dolusu yüz binlerce başıboş dosyanın ana thread üzerinde senkronize I/O darboğazı oluşturduğu, kamera her hareket ettiğinde diski kilitleyen ve komşu harita sınırlarında dikiş izleri (seam drift) yaratan 20 yıllık eski kafayı projeden söküp attım.
136 harita klasörünü, 10.823 adet başıboş dosyayı ve diskteki 1.5 milyon satırlık metin/veri çöpünü tek hamlede temizledim. Yerine, hem istemcinin hem de WorldEditor'ün sıfır dinamik bellek kopyalamasıyla doğrudan okuyup yazabildiği birleşik ikili kapsayıcı mimarimi (.ashenfallmap) sıfırdan geliştirdim.

Eski Yöntem Neydi ve Neden Terk Edildi?​

Geleneksel harita yapısında tek bir açık dünya haritası; yüzlerce 000000/, 000001/ alt klasörüne bölünmüş height.raw, tile.raw, attr.atr, water.wtr, minimap.dds ve metin tabanlı areadata.txt, setting.txt gibi binlerce başıboş dosyadan oluşuyordu.
Bu ilkel düzen şu devasa problemleri beraberinde getiriyordu:
  • Senkronize CPU ve I/O Darboğazı: Karakter haritada yürürken 3×3'lük chunk penceresi her güncellendiğinde ana thread üzerinde onlarca metin dosyası satır satır ayrıştırılıyor (LoadMultipleTextData), LZO ile sıkıştırılmış küçük dosyalar için pack kilitleri (lock) tetikleniyor ve anlık mikro takılmalar (stutter / hitch) kaçınılmaz oluyordu.
  • Sınır Kayması (Border Drift) ve Dikiş İzleri: Komşu arazilerin yükseklik sınırları her iki parça tarafından ayrı ayrı saklanıyordu. Yıllar içinde bu veriler birbirinden kopmuştu; örneğin lonca haritasında (guild_war4) iki komşu chunk arasında 50 metrelik uçurumlar ve uyuşmayan 370'in üzerinde kenar noktası mevcuttu.
  • Yanlış Klasörlenen Objeler: 46.000'den fazla dünya nesnesinin 549 tanesi üzerinde durduğu arazinin değil, tamamen farklı koordinatlardaki klasörlerin içine yazılmıştı. Bu durum, kameranın görüş açısına göre ağaçların ve binaların aniden havadan belirmesine ya da erken kaybolmasına (pop-in) sebep oluyordu.
  • Çapraz Harita Doku Bozulması: Editörde tek bir haritanın zeminini düzenleyip kaydettiğinizde, ortak kullanılan paylaşımlı textureset dosyası ezildiği için alakasız diğer 10 haritanın zemin kaplamaları geri dönülemez şekilde bozuluyordu.

Yeni Sistem Nasıl Çalışıyor? (Teknik & Mimari Altyapı)​

Yeni mimaride artık klasörler, metin dosyaları ve parça parça raw array'ler yok. Harita başına yalnızca tek bir deterministik binary kapsayıcı (.ashenfallmap) bulunuyor.
Kod:
Genişlet Daralt Kopyala
┌────────────────────────────────────────────────────────────────────────┐
│                        .ashenfallmap MİMARİSİ                             │
├───────────────────┬────────────────────────────────────────────────────┤
│ 64-Bayt Header    │ PNG-stili Sihirli Baytlar (Magic), Sürüm, CRC-32   │
│ Section Table        │ 32-Bayt Hizalı Bölüm Başlıkları (Tag, Offset, CRC) │
│ Sabit Katmanlar   │ META (Ayarlar), STRS (Dizgi Havuzu), LAYR, CHNK     │
│ Raster Verileri   │ HGHT (129² uint16), TILE (256²), ATTR, WATR, MINI  │
│ Varlık Tabloları  │ INST (48-Bayt POD), AMBI (24-Bayt POD)             │
└───────────────────┴────────────────────────────────────────────────────┘

1. Sıfır Kopyalamalı Bellek Eşleme (Zero-Copy Memory-Mapped I/O)​

Dosya mimarisi mmap ve doğrudan işaretçi (pointer) aritmetiği için 16-bayt sınırlarına hizalandı. Harita arşivi istemciye EPack32 Type 0 (Stored / Sıkıştırmasız) olarak paketleniyor.
İstemci haritayı açtığında diski belleğe tek bir sanal adres uzayı olarak eşliyor (CEterPack::Get). Pack arşivi açılırken CPU üzerinde hiçbir global kilit (lock) oluşmuyor; sayfalar (pages) belleğe yalnızca dokunuldukça (page fault mantığıyla) işletim sistemi tarafından sıfır gecikmeyle çekiliyor.

2. Katman Bazlı Yükseklik Delta Filtreleme & Blob DEFLATE​

Harita geometrisi ve raster katmanları dosya içinde katı bir determinizmle organize edildi:
  • Filtrelenmiş Yükseklik Verisi (Height Delta Planes): 129×129'luk ham yükseklikler komşu piksel delta farklarına ayrılarak düşük ve yüksek bayt düzlemlerine (planes) bölündü. Bu filtreleme, salt DEFLATE sıkıştırmasına kıyasla yükseklik verisinde fazladan %27 boyut tasarrufu sağladı.
  • Türetilebilir Verilerin Tasfiyesi: Komşu arazilerin sınır halkaları (rings), yüzey normalleri ve splat haritaları dosyada saklanmıyor; yükleme anında bitişik komşulardan dikişsiz olarak türetiliyor (AssembleHeightRing). Kenar yükseklikleri matematiksel olarak eşitlendi ve drift problemi tamamen ortadan kaldırıldı.
  • Gömülü Doku Paleti (LAYR): Her harita kendi zemin katmanlarını ve UV ölçeklerini kendi içinde taşıyor. Editörde bir haritayı düzenlemek artık başka hiçbir haritanın zeminini etkileyemiyor.

3. 48-Bayt Sabit POD Nesne Dizilimi​

Metin tabanlı areadata.txt ayrıştırması tamamen kaldırıldı. Binalar, zindan blokları, ağaçlar ve efektler harita koordinatlarına göre sıralanmış 48 baytlık sabit POD (Plain Old Data) struct'ları olarak saklanıyor. Yanlış klasörlenen 549 nesne matematiksel olarak üzerinde durdukları doğru chunk sınırlarına taşındı.

Performans & Optimizasyon Kazanımları​

Bütün projedeki 136 haritanın (106 bağımsız harita, 30 hafif alias) tamamı yeni formata geçirildiğinde elde edilen net mühendislik verileri:
  • 1.5 Milyon Satır ve 10.823 Dosya Çöpe Gitti: +1.321 / -1.502.382 satırlık diff ile kaynak koddan tüm ilkel loader'lar, metin parser'lar ve başıboş klasörler kalıcı olarak kazındı.
  • %78 Disk Ayak İzi Küçülmesi: Eski sistemde 313 MB tutan dağınık harita verisi, yeni mimariyle birlikte kayıpsız olarak 67.4 MB boyutuna indi. En büyük 6×6 kar haritasının (map_n_snowm_01) tüm zemin ve nesne geometrisi yalnızca 2.37 MB.
  • 26 Milisaniyede 136 Harita: 136 haritanın tamamını açıp başlık ve CRC doğrulamalarını tamamlamak C++ motorunda toplamda yalnızca 26 ms sürdü.
  • 0.06 ms Chunk Açılma Hızı: Karakter arazide ilerlerken yeni bir parçanın bellekten çekilip çözülmesi 0.06 milisaniyeye (60 mikrosaniye) düştü. Chunk geçişlerindeki mikro takılmalar, anlık FPS düşüşleri ve disk I/O beklemesi tamamen sıfırlandı.
  • Determinizm ve Pre-Commit Güvenliği: Yazılan MapDocument mimarisi sayesinde, harita WorldEditor ile açılıp hiçbir düzenleme yapılmadan kaydedildiğinde bile diskteki dosyanın tek bir baytı dahi kaymıyor; byte-identical determinizm korunuyor. Repoya eski tip harita dosyalarının girmesi ise CI/CD hattına eklediğim pre-commit gate kontrolüyle engellendi.
Gelecekte araziyi doğrudan GPU üzerinde Compute Shader veya Indirect Draw ile tek geçişte çizeceğimiz Full-Map Terrain Renderer mimarisine geçiş için gereken temiz, contigous ve modern veri altyapısını böylece tamamlamış oldum.

​

[Devlog] Bölüm II: Sunucu Tarafının Tasfiyesi, Tek Gerçeklik Kaynağı (.ashenfallworld) ve Modern Editör Altyapısı​

İstemci ve WorldEditor tarafında .ashenfallmap formatına geçip disk I/O darboğazını sıfırladıktan sonra masada devasa bir kambur kalmıştı: Sunucu çekirdeği (game).
20 yıldır istemci dosyalarından bağımsız yaşayan, sunucu tarafında yüzlerce klasörde tutulan Setting.txt, Town.txt, LZO sıkıştırmalı server_attr, regen.txt, npc.txt ve boss.txt dosyaları hala hayattaydı. İstemcide olmayan dosyaların sunucuda aranması, sunucuda olan koordinatların istemcideki atlasinfo.txt ile uyuşmaması, büyük/küçük harf tutarsızlıkları (devilsCatacomb, CapeDragonHead) ve monkey dungeon gibi kopya haritaların diskte yüzlerce megabaytlık gereksiz veri tekrarı yaratması tam bir krizdi.
Bu ikinci fazda sunucunun tüm eski harita yükleyicilerini kalıcı olarak projeden kazıdım. Artık istemci, editör ve sunucunun tamamı aynı ikili dosyalardan ve tek bir global dünya tanımından besleniyor.

Eski Sunucu Mimarisinde Neler Vardı ve Neden Çöpe Gitti?​

  1. İki Ayrı Dünyanın Senkronizasyon Çilesi:
    İstemci hangi haritaya ışınlanacağını root/atlasinfo.txt üzerinden metin okuyarak çözerken, sunucu share/locale/.../map/index dosyasını tarıyordu. Yıllar içinde bu iki dosya birbirinden koptuğu için 22 haritaya (season1/..., season2/..., map_a2) oyuncu asla ışınlanamıyor ya da yanlış yere düşüyordu.
  2. Kopya Haritaların Veri Yükü:
    Üç maymun zindanı veya lonca köyleri gibi haritalar aynı arazi geometrisine sahip olmasına rağmen, sunucu tarafında her biri için ayrı ayrı server_attr ve metin dosyaları tutuluyordu.
  3. 20 Yıllık Ölü Kod ve Verisiz Sistemler:
    Sunucu boot olurken hiçbir haritada bulunmayan hayalet dungeon.txt dosyalarını arıyor, içi boş MonsterAreaInfo yapıları parse ediliyor ve event_flag bağımlı sahte event attribute bitleri için gereksiz CPU döngüsü harcanıyordu.

Yeni Sistem Nasıl Çalışıyor? (Format 1.1 & .ashenfallworld)​

Sunucuya .ashenfallmap okuma kabiliyetini doğrudan C++20 standardında ekledim (Shared/Map/MapFile.cpp).
Kod:
Genişlet Daralt Kopyala
┌────────────────────────────────────────────────────────────────────────┐
│                   BİRLEŞİK SUNUCU/DÜNYA MİMARİSİ                       │
├───────────────────┬────────────────────────────────────────────────────┤
│ maps/world...     │ .ashenfallworld: Global koordinatlar, Server ID,   │
│ (PLCE + STRS)     │ Harita boyutları, çakışma kuralları (Single-Source)│
├───────────────────┼────────────────────────────────────────────────────┤
│ Format 1.1 Ekleri │ TOWN (Doğma/İmparatorluk noktaları, 36-Bayt POD)   │
│ (Opsiyonel)       │ SETS (İsimli spawn setleri: boot, boss, event)     │
│                   │ SPWN (36-Bayt sabit POD spawn kayıtları)           │
└───────────────────┴────────────────────────────────────────────────────┘

1. Tek Dünya Tanımı:​

Eski atlasinfo.txt ve sunucunun index dosyasını tamamen sildim. Yerine tüm haritaların global dünya üzerindeki $(X, Y)$ taban koordinatlarını, sunucu ID'lerini ve boyutlarını tutan ikili world.ashenfallworld dosyasını getirdim.
  • Sıfır Veri Tekrarı (Aliasing): Maymun zindanları ve lonca köyleri artık diskte sadece birer ufak alias dosyasından ibaret. Zemin geometrisi ve çarpışma verisi ana araziden okunuyor; her kopya sadece kendi TOWN ve SPWN verisini saklıyor.
  • Yetkili Koordinat Sistemi: Harita dosyaları (.ashenfallmap) artık kendi içinde base koordinatı tutmuyor (her zaman 0). Haritayı dünyanın neresine koyacağına yalnızca dünya dosyası karar veriyor.

2. Sabit 36-Bayt POD Spawn Altyapısı (​

Metin tabanlı regen.txt, npc.txt ve boss.txt dosyalarının tamamı ikili formatın içine taşındı:
  • Spawnlar; türü (m, g, r, s), agresiflik bayrakları, yönü, koordinatları ve süreleri 36 baytlık sabit POD struct'ları olarak dosyada yer alıyor.
  • İsimli Setler (SETS): Haritanın açılışta doğuracağı canavarlar (SET_BOOT), zindan fonksiyonlarının tetikleyeceği boss/özel gruplar (instance_regen, npc) isimli setler altında gruplandı. Sunucu artık diskten dosya yolu aramak yerine hafızadaki haritadan ismiyle seti çekiyor (GetSpawnSet("instance_regen")).

3.​

Sunucunun harita açılışında okuduğu LZO sıkıştırmalı server_attr dosyalarının tamamı silindi. SECTREE_MANAGER, sectree bloklarını ve çarpışma bitlerini doğrudan .ashenfallmap içerisindeki ATTR raster katmanından derleme anında oluşturuyor (100 birimlik grid 50 birimlik 2x2 sunucu hücrelerine genişletiliyor).

4. WorldEditor: MonsterAreaInfo Tasfiyesi ve Yeni Spawns Katmanı​

Eski MFC/DirectX 9 döneminden kalma, arayüzü kilitleyen ve server_map klasörlerine bağımlı olan MonsterAreaInfo kodlarının tamamı çekirdekten silindi.
  • Editöre modern ImGui tabanlı bağımsız bir Spawns paneli inşa edildi.
  • Canavar isimleri doğrudan mob_proto ve sunucunun group.txt / group_group.txt hiyerarşisinden okunarak harita üzerinde gerçek isimleriyle ("Yabani Köpek x3" vb.) görselleştirildi.
  • Sahne üzerinde kutular, yön okları, kasaba ve 3 imparatorluğun dirilme noktaları (Town/Empire markers) milimetrik ızgarada doğrudan düzenlenebilir, boyutlandırılabilir ve anında haritanın ikili formatına kaydedilebilir hale getirildi.
  • 64 adımlık hafif prosedürel bir geri alma (Undo/Redo) mimarisi kuruldu.

Doğrulama ve Net Kazanımlar​

  • 680 Sunucu Dosyası Çöpe Gitti: Server/Binary/share/locale/english/map klasörünün tamamı (Setting.txt, Town.txt, server_attr ve metin tabanlı spawnlar dahil 680 dosya) silindi. Sunucu artık tüm harita verilerini tek bir MAP_FOLDER parametresiyle istemcinin ikili paket klasöründen okuyor.
  • Tam Determinizm (--verify-maps): Geliştirilen geçici debug doğrulayıcı ile 65 harita, 12.704 sectree, 208 milyon attribute hücresi ve 20.969 spawn kaydı eski metin okuyucularıyla bayt bayt kıyaslandı; bilinen tasarım kararları dışında 0 hata / 0 uyumsuzluk ile doğrulandı. Fuzzing testlerinde üretilen 16.672 bozuk dosya senaryosunun tamamı motor tarafından anında reddedildi.
  • Sıfır Uyarı, Sıfır Hata: Hem SV-Debug hem SV-Release derlemeleri 0 hata ve 0 yeni uyarı ile inşa edildi.
  • Ölü Kodların Temizliği: Verisi olmayan dungeon.txt ayrıştırıcıları, d.spawn gibi hayalet fonksiyonlar, sahte event attribute taramaları ve mob drop tablolarındaki gizli kalmış tanımsız bellek gölgeleme hataları tamamen ayıklandı.
İstemci, sunucu ve editör artık aynı binary dilden konuşuyor. Yıllardır harita düzenlerken yaşanan "sunucuya ayrı at, istemciye ayrı paketle, atlasinfo ile index'i eşitle" çilesi tamamen bitti; harita hattı profesyonel modern oyun motorları standardında tek kaynak (single source of truth) haline getirildi.
 
Geri
Üst