Under the hood

Kiln, our own engine

Shardbound started on raylib and outgrew it. Kiln replaces it with our own renderer on modern graphics APIs, so the game can use upscalers and ray tracing. Every number below was measured on an Apple M4 at 2646×1602 unless it says otherwise.

One renderer, two APIs

Metal on macOS, Vulkan everywhere else. Shaders are written once in GLSL and turned into SPIR-V and Metal Shading Language at load time, then cached on disk.

Metal · Vulkan 1.2the Vulkan path is checked through MoltenVK on macOS

Ray-traced shadows

On Apple silicon, the sun's shadows are traced: one ray per pixel at half resolution, in a pass of its own, reused by the next frame. Elsewhere, cascaded shadow maps take over.

~2.7 msfor the ray-traced shadow pass

Temporal upscaling

Our own temporal upscaler, in the manner of FSR 2, runs on every backend; MetalFX where it exists; TAA at native resolution. Jitter, motion vectors and history clipping are all ours.

102–111 FPSat 67% render scale, against 84–92 native (forest, town, lake)

A streamed world

The world is cut into 64-metre chunks, each generated from the world seed alone. The client loads the nearest chunks first and drops the far ones; the server builds collision only where players are.

64 × 64 mchunks, the same on client and server

Surface relief

Parallax occlusion with self-shadowing on the ground and on walls: stones stand proud and mortar sinks. On walls the relief cuts the silhouette, so edges follow the stones instead of a flat line.

+2.8 msfor relief on the town plaza (15.9 → 18.7 ms)

Instancing and tree impostors

Props are culled and drawn instanced with two simpler levels of detail. Past 60 m a tree becomes an impostor: one quad showing the tree baked from eight directions, lit like everything else.

1.8×farther tree draw distance; Whisperwood props 1.99 → 1.42 ms

Particles from a fixed pool

Effects are data. Every particle lives in one pool reserved at start, so playing an effect never allocates, and each blend mode is one instanced draw.

12,000particles at most; ~11,800 live cost 0.14 ms to update

Lazy, compressed textures

Textures are block-compressed on every core and cached. Character parts load as placeholders and become real textures the first time they're drawn.

2.8 GB → 1.1 GBtexture memory in town; a full outfit's 24 textures in 10 ms

Servers that scale out

The world simulation is C++; accounts, characters, chat and the world directory are Go services on Postgres. Continents run as separate servers, and busy ones split into layers on Kubernetes.

~800players per world server process in our load tests

The whole stack

Written in-house

Engine, client, world server, services, launcher and tools are our own code. Game content (classes, quests, monsters, their AI) is Lua that the server loads at start, and the same data feeds this website.

Client and engine
C++20, Metal and Vulkan
World server
C++20, authoritative: it decides damage, loot and experience
Services
Go on Postgres: accounts, characters, chat, the world directory
Content
Lua 5.4 data and scripts
Hosting
Kubernetes in the EU, world servers through Agones
Platforms
macOS on Apple silicon (tested); Windows and Linux on Vulkan (untested on real hardware)

Next for Kiln

Planned, not started

GPU-driven culling and cluster levels of detail, for when denser art arrives. We'll write about it here when it starts, with numbers.