voxelforge

Silnik voxel z grywalnym demem w przeglądarce. Renderer celuje w WebGL2/WASM, a rdzeń symulacji buduje się natywnie i jest pokryty testami - dziesiątki tysięcy asercji.

voxelforge
TL;DR

Silnik voxel z grywalnym demem w przeglądarce. Sztuczka polega na tym, że rdzeń symulacji nie wie nic o rysowaniu - dzięki czemu logikę da się testować bez GPU, dziesiątkami tysięcy asercji.

Wprowadzenie

voxelforge to silnik voxel z grywalnym demem działającym w przeglądarce. Renderer celuje w WebGL2 i WASM, ale najciekawsza decyzja nie dotyczy grafiki, tylko architektury: rdzeń symulacji jest całkowicie odcięty od rysowania. To on trzyma voxele, zasady i sąsiedztwa, i nie wie nic o tym, że cokolwiek pojawia się na ekranie.

Ta jedna decyzja zmieniła charakter projektu. Zamiast silnika, który działa tylko wtedy, gdy uruchomisz go z kartą graficzną, powstała biblioteka logiki, która chodzi wszędzie i da się ją przetestować jak zwykły kod. Reszta case study to opowieść o tym, dlaczego to się opłaciło.

Silnik voxel musi robić dwie ciężkie rzeczy jednocześnie: szybko rysować świat i poprawnie go symulować. Oba łatwo zepsuć tak, że na pierwszy rzut oka wygląda dobrze, a mimo to jest źle - blok znika o jedną klatkę za późno, sąsiedztwo liczy się o jeden voxel obok. Te błędy nie krzyczą, tylko po cichu psują świat.

Kiedy renderowanie i logika są splecione w jednym kodzie, nie da się ich rozdzielić ani przy testowaniu, ani przy debugowaniu. Nie wiadomo, czy blok znikł źle, bo symulacja policzyła go źle, czy dlatego, że renderer go źle narysował. Rozplątanie tych dwóch światów było najważniejszą rzeczą w całym projekcie.

Rdzeń, który nie wie o GPU

Rozwiązanie było zarazem najprostsze: rdzeń symulacji to czysta logika, a renderer bierze jej stan i zamienia go na trójkąty. Żadna część rdzenia nie dotyka canvasu ani GPU. Interfejs między nimi jest wąski i jawny, więc renderer może być dowolny, a rdzeń o tym nie wie.

core.ts · typescript
interface SimulationCore {
  tick(dt: number): void;
  voxelAt(x: number, y: number, z: number): Voxel;
}

interface Renderer {
  draw(core: SimulationCore): void;
}

Kto za co odpowiada

WarstwaRolaZależność od GPU
Rdzeń symulacjivoxele, zasady, sąsiedztwabrak
Rendererzamiana stanu na trójkątyWebGL2 / WASM
i
Informacja

Skoro rdzeń nie zależy od GPU, buduje się go natywnie i testuje jak zwykłą bibliotekę. Renderer celuje w WebGL2 i WASM, ale logika przechodzi testy nawet na maszynie CI bez karty graficznej.

Testy zamiast nadziei

Ta separacja to nie akademicki luksus. Dzięki niej symulacja ma pokrycie testami liczone w dziesiątkach tysięcy asercji - a to jedyny sposób, żeby o silniku voxel powiedzieć, że jest poprawny, a nie tylko "wygląda dobrze". Błędy o jeden voxel obok wychodzą w teście, a nie po godzinie grania.

tysiące
voxeli w chunku
dziesiątki tysięcy
asercji w testach rdzenia
0
zależności rdzenia od GPU

Efekt: demo, które rośnie bez strachu

Na wyjściu jest grywalne demo w przeglądarce, które można rozwijać, nie bojąc się, że przy okazji rozjedzie się logika świata. Ponieważ rdzeń jest przetestowany osobno, zmiana w rendererze nie może po cichu zepsuć symulacji, a zmiana w symulacji od razu wychodzi w asercjach. Silnik nie jest tylko efektowny - jest sprawdzalny.

Więcej projektów

Inne realizacje z tej samej kategorii - zobacz, jak podchodzimy do podobnych wyzwań.

Masz podobny projekt?

Napisz do nas - wycena jest bezpłatna i wraca w godzinę.