A night scene from a game running on the engine.

The WebGPU game engine for the web. And for everywhere you ship after that.

In TypeScript, drawing through WebGPU and through WebGL2 wherever a browser has no WebGPU. Rendering, deterministic physics, animation, audio, scripting, netcode and an editor in one engine, and the same build installed on desktop, on Android and on Steam.

In production use, version 4.13.5. Open source under Apache 2.0.

npm i @driftengine/coreHow to use itSee nine live scenes

What it is

  • A 3D engine for the browser rather than a rendering library: the loop, the physics, the audio and the input ship with the renderer.
  • The same build also installs as a desktop or mobile application, so a product can ship in a tab and in a store without a second version of it to keep in step.
  • Built for anything that has to load in seconds and hold a steady frame on a phone: games, playable ads, product configurators and interactive video.
  • Its own model container streams a large file while it is still downloading, so a viewer opens on a recognisable shape inside the first per cent of it.
  • Deterministic by design, so a run can be recorded once and replayed exactly.
  • Ready for agents without depending on one: a deterministic floor runs inside the fixed step, so an agent whose model is slow, absent or over budget loses quality and never motion.
  • A runtime first, with the editor beside it. The engine runs without the editor, a game that never opens it carries none of it, and a team that has its own window, layout and workflow keeps them.
  • Its own renderer. The shaders, the shadow maps and the GPU state are written here rather than wrapped from elsewhere.
  • Focused. It draws, simulates and makes sound, and leaves the rest of your game to you.
  • One person's work, in the open. There is no support contract behind it and no team on a rota: what there is instead is the source, the tests it is held to, and a maintainer who answers.

What you get here

Fewer pixels shaded, the whole picture shown

DriftTR draws the scene smaller, jittered a fraction of a pixel differently every frame, and a compute pass rebuilds the full-size picture from this frame and the history of the ones before it. At a ratio of 1.5 the scene shades 44% of the output's pixels and saves the rest of the fragment work.

a measured scene from 3.1 ms of GPU time to 2.45

Light that bounces, and follows the scene

DriftRay marches rays through signed distance fields in compute, a few hundred a probe, into the probe grid the shading already reads. A baked grid keeps the light it was baked with; a traced one refreshes a few probes every frame, so a repainted wall or a moving sun changes the bounce around it.

WebGPU · no ray-tracing hardware needed

Every lamp in a city lit

A frame shades a limited number of lights exactly. DriftLight sums every fixed light once into a volume of light and direction, shadowed by the scene's own walls, and the shading reads it wherever the exact lights do not reach, crossfading over two metres so nothing pops.

a gallery of seven hundred candles, every wall lit

The GPU decides what to draw

Each frame the GPU culls every object against the view, picks the detail each has earned, culls the surviving clusters of triangles against a depth pyramid, and draws what is left with one indirect draw into a visibility buffer, shaded with one dispatch per material. The CPU says where the camera is.

2,500 meshes in one indirect draw · WebGPU

Worlds bigger than a float

The simulation keeps positions in double precision and never rebases, and the renderer subtracts an origin that moves a cell at a time. Cells stream in ahead of the camera by prediction, and the people in them freeze outside the simulated radius and carry on from where they were.

a town three thousand kilometres from the origin

A run replays exactly, and so does a network game

The simulation advances in fixed steps from seeds that hold across versions, so the same inputs give the same session to the last bit on any machine. Replays, rollback netcode that re-runs the last ticks when a late input lands, and an editor that scrubs a running game backwards all follow from it.

fixed 1/60 · rollback · the editor's scrubber

A video of a room becomes a place

DriftCapture turns a clip of a place into a surface, a collider a character can stand on, colours separated from the light they were filmed under, and regions proposed as entities. It runs on the player's own device, and each stage states how mature it is.

72,615 bytes gzipped · nothing fetched

No browser in the process

The same build runs on a native window and a native GPU through a host, and the published scenes come back identical to the browser pixel for pixel. The engine did not change to allow it: everything it needs from a page it already took as a capability the caller supplies.

the same build · identical frames

Change a rule while the game runs

Behaviour written in DriftScript, the engine's typed scripting language, is compiled when it is saved and swapped into the running game, and the game's records keep their values, so a scene carries on from where it was under the new rule.

typed · reloads in place · state kept

It listens to the music

A layered mix that responds to what the game is doing, and analysis that finds the beats in a track ahead of time. A recording can be cut to the music, and an exported clip carries the same mix the player heard rather than an approximation of it.

beat detection · offline render matches live

It records itself

Capture, pacing and encoding are part of the engine. A player finishes and leaves with a video already at the right size, the right frame rate and in the right container for wherever they are going to post it.

mp4 and webm · up to 240 fps

A large model opens before it has arrived

Its own container lays its payloads out in the order a viewer needs them and can carry a coarse version of the whole model ahead of the geometry it stands for, so an ordinary sequential fetch refines the picture as bytes land. There are no range requests, and nothing is asked of a server beyond serving one file.

zero copy load · a recognisable shape in the first per cent

Built with it

A frame from Drift Cut.

Drift CutAvailable now

A tool that cuts a 3D model to a song and exports the result vertical.

A model streamed into a browser, a camera edit placed on the beats an analyser found in the track, and a recording that carries the mix the viewer actually heard. Nothing in it is a player, a world or a run, which makes it the sharpest test of the boundary this engine holds itself to.

A frame from Driftways.

DriftwaysPlayable now

An outdoor racer whose world is generated fresh every day.

Long banked routes, open water, weather and a full day and night cycle, laid out to the beat so every replay lands in time with the music.

A frame from BACKROOMS.

BACKROOMSPlayable now

An indoor survival game, lit entirely by what you carry and what you find.

Dense interiors where every light is dynamic and every shadow is cast in real time. A world with no sky, which is the opposite of the first, on the same renderer.

Three products on one runtime, and no two of them alike: a game outdoors, a game indoors, and a tool that is not a game at all. The question this engine holds itself to is whether a second, unrelated project would want it unchanged, and building one is the only way that question gets answered.

The whole engine

Render

Dynamic lighting and shadows, water, sky and weather, on either backend.

Both graphics APIs behind one surface, chosen at startup from the device the browser actually hands over rather than from what it advertises, and reported rather than inferred. Lights have a physical size and their shadows follow from it, so a bulb throws a crisp edge and a flame a soft one that widens with distance from the caster; clustering shades a surface against the lights reaching its own part of the frustum, which takes a scene from sixteen point lights to three hundred and twenty, and every fixed light past those is summed into a volume the walls shadow, so a hall of candles is lit by every candle. Probes on a grid light the scene from the place it is in, so a polished metal mirrors that place rather than an average of the room, and where the newer API is available the light between surfaces is traced through a distance field into the same probes. Water is a wave simulation with reflections, smoke is advected and dissipating, and one wind sampled once a frame moves everything that answers to air, so a canopy and its shadow bend together. An optional screen space chain adds occlusion, glow, a defocus that follows the lens you describe, motion blur, exposure that adapts to the frame and to each region of it, darkened corners, grain and a graded look from a table an artist exported. Glass bends what is behind it and takes colour out of it, and imported models arrive wearing their own normal, occlusion, roughness, metallic and emissive maps.

In the manual: Meshes and geometry, Materials, Lights, Shadows, Light in the air, Sky and atmosphere, Fog and weather, Water, Reflections, Translucent and additive meshes, Decals, Post-processing, Colour and motion, DriftLight, DriftRay, DriftTR, The GPU-driven pipeline, Particles, Custom passes and compute, WebGPU and WebGL2, Render quality

Physics

Rigid bodies, joints, characters and cloth, in a package that imports nothing else.

A full rigid body simulation: a dynamic bounding volume tree for the broad phase, contact manifolds, a substepped solver whose constraints are soft, islands that sleep together, six joint types with motors and limits, raycast and shapecast and overlap queries, sensors on a thirty two layer mask, ragdolls, a raycast vehicle and position based cloth. Because the constraints carry a stiffness in hertz rather than a fraction of penetration, raising the substep count buys accuracy without moving anything: a stack rests at the same height at four substeps and at eight. The character controller sweeps rather than steps, so it climbs a kerb, slips off a rim it is perched on and works its way out of a corner it would otherwise stand in. All of it runs with no renderer present, which is what lets a server simulate the same world a browser draws. Islands share no bodies, so they can be solved on worker threads and reach the same bits in the same order; that is a switch a product throws rather than a default, because the shared memory it needs is bought with a header pair on the whole site and a page is nobody else’s to change.

In the manual: Rigid bodies, Joints, Queries and colliders, Characters, Vehicles, Ragdolls and cloth

Nav

A mesh or a graph, a route across it, and a point to steer at.

A graph rather than a navigation mesh, and the reason is what consumers keep arriving with: a network already, roads or corridors or the ways between rooms, which a mesh would ask them to throw away and rebuild from geometry. Edges carry a cost that is not a distance, so mud, a hill, a toll and a road an agent should prefer are all the same number. The search allocates nothing per query and breaks ties on the node index, so a route does not depend on the order the edges were declared in and a replay walks the road it walked before. Following aims at a point a lookahead ahead along the line rather than at the next waypoint, which is the difference between cutting a corner and stopping to turn at each one, and progress is recovered from the agent own position each update, so being shoved costs progress and never correctness. Open ground is the half a graph cannot answer, so there is a mesh beside it: geometry becomes a walkable voxel field, a watershed partition and convex polygons, and a route across them is funnelled into a straight line rather than a walk between polygon centres. Two layers are kept under an overhang, which is why it is voxels and not a heightfield, and one search serves both because a mesh becomes a graph before it is walked. What neither does is steer: a path is a function of geometry and the engine owns one, but how a character moves along it is the feel of one particular game.

In the manual: Navigation

Capture

A video of a room becomes a scene you can walk around in, on the player machine.

A clip goes in and a place comes out: frames chosen by motion, a depth model run on the same device that will draw the result, the views fused into a signed distance volume, marched and simplified into a surface. Then the things that make a surface a scene rather than a picture of one: a collision mesh a character controller stands on, a navigation mesh baked from the same geometry, a separation of the colour a surface reflects from the light it happened to be photographed under, and a cut into regions with a proposal for what each one might be. Nothing is sent anywhere. The models run through the same inference path the renderer uses, which is why this is a package and not a service, and the weights are fetched once at a pinned revision by a tool in the repository rather than shipped inside anything. What it will not do is pretend: every stage reports how far it got and how much it trusts the answer, a proposal arrives unlabelled for a person to accept or reject rather than guessed at, and the scale is the depth model claim rather than a measurement, which is written down beside the metres instead of left to be discovered.

In the manual: DriftCapture

Textures

A material as a small program over a learned field, rather than as a stack of images.

A texture here is a decode program: a compact latent, a graph of operations over it and a small network, evaluated where it is sampled. What that buys is not only size. Tiles are addressed by the hash of their contents, so two that are identical are stored once however many materials reach for them; a channel knows what it means, so a normal map reduces as a normal map should rather than as three numbers; and residency is decided by running the simulation forward a few steps with nothing drawn and asking where the camera will be, then putting it back exactly as it was. That is a thing an engine can only do if it can already rewind, which this one can because the netcode needed it, and it is the difference between fetching a tile before it is wanted and fetching it after the frame that wanted it has gone. A tile that will never arrive stops being waited for instead of blocking the queue behind it. Surfaces can also be written to at runtime, and those writes go into the same log the input does, so a replay burns the same wall rather than a clean one.

In the manual: DriftTexture, Texture streaming, Writable textures, Texture arrays and surface effects

Native

The same build on a native window and a native GPU, with no browser in between.

The engine runs on a native window with a native graphics device and no browser anywhere in the process. It is a host rather than a port: nothing in the engine changed to make it work, because everything the runtime needs from a page it already took as a capability the caller supplies. What the host does is answer those capabilities with native ones, and the proof that the seam is real is that the published scenes come back identical to the browser pixel for pixel. It starts in well under a second into an archive a fraction the size of a bundled browser, and it can hand a game the graphics limits of the actual adapter rather than the conservative tier a browser reports. What a page has that it does not is written down with the same care as what it gains. Assistive technology, a second graphics backend beneath it and a document around the canvas are all things a browser brings and this does not, each recorded as absent rather than left to be discovered, and the platforms it is built for today are stated rather than implied.

World

A world larger than the numbers a graphics card counts in, walked without drift.

Past about sixteen million units a single precision coordinate counts in whole numbers rather than fractions, which is why large worlds shimmer and why most engines move the world around a stationary player. This one keeps simulation coordinates absolute and at double precision and moves only the rendering origin, in whole cells rather than continuously, so nothing is re quantised every frame and a stored replay cannot come out differently for a session that happened to cross a boundary. Cells stream by where the camera is about to look rather than only by where it is, computed from the same forward run the textures use. A cell outside the simulated radius freezes whether or not its contents are still in memory, and only a frozen cell may be released, which keeps how much memory a machine had from changing what the simulation computes. A frozen cell still contributes its digest, because a run that agrees only once everything has thawed is a run that disagreed for a while. What that costs is stated rather than discovered: something in a released cell does not walk anywhere, does not age and does not finish what it was doing.

In the manual: Large worlds, Hierarchical detail, Worlds from a kit

Tools

The editor panels, in the shipped game, with an undo stack under them.

The inspector, the console, the frame profiler and the network panel are a package a game can carry into production, at a few thousand bytes, and a game that does not carry them pays nothing for their existence. The line drawn is what a panel needs rather than what it shows: anything that wants a project on disk stayed in the editor, and what is left works against a running world. Every edit a panel makes is a command on a stack rather than a direct write, so an in game change is undoable without the game arranging anything for it, and the same command a designer issues at rest is the one recorded and replayed when it is issued while the simulation is running. That last point is the one that costs elsewhere: an edit made while a game is playing is normally applied straight to the world, which leaves a recording that cannot reproduce what happened next. Here it goes into the log and the simulation is run again from that moment, so the change survives being scrubbed away from and back to, and a session that was edited halfway through still replays exactly.

Behaviour

A routine that can be watched, held, stepped, and interrupted without being lost.

A routine can be written as a table and a loop, and plenty are. What that cannot do is be looked at while it runs, or held and advanced one node at a time, or interrupted and resumed. The last is the one that costs: a branch that takes over from a lower priority one suspends what it displaced instead of resetting it, so an agent that abandons an errand because it started raining picks it up at the step it had reached rather than setting out again. Most implementations reset on abort, which is why the difference is worth naming. The tree is data, with a name on every node, so what an agent is doing is a question the tree answers rather than one a script has to keep its own copy of and watch drift. Around it sit the parts a routine usually has to invent for itself: steering that arrives at a point without orbiting it, perception with a field of view and a memory of what was seen rather than a straight distance check, and a blackboard that is read and written by name. All of it advances on the simulation clock, so an agent behaves the same in a replay as it did live.

In the manual: Agents and behaviour

Chemistry

Materials that are made of something, and burn, cook, rust and rot accordingly.

A material is a composition over real chemical species, and what happens to it follows from that rather than from a state machine. Heat is stored as enthalpy instead of temperature, so a phase change is something the solver produces rather than an event somebody scheduled: water sits at its boiling point until the last of it has gone, and a stew at altitude never gets hot enough to brown. An object has depth, in shells of equal mass, which is why a twig catches and a log put next to the same fire does not. Reactions are checked against an element table when they are registered and costed by their own formation enthalpies, so the heat a fire gives off is derived rather than tuned. One atmosphere carries oxygen toward the flame and smoke away from it, so a sealed room runs out of air and the burning stops with fuel still in it. Thirty nine ready materials come in seven optional sets, and a game that wants a campfire takes one of them for under a kilobyte.

In the manual: Chemistry

Animation

Rigs, clips, blending and reach, sampled as a function of the time you give it.

A skeleton resolves a pose into a skinning palette, and a clip is sampled as a pure function of the time you supply rather than from a clock of its own, so a second of an animation looks the same in a replay as it did live. Layers blend and add, a state machine crossfades between them over parameters you name, two bone inverse kinematics puts a hand or a foot where you want it, and morph targets carry a face. A rig imported from glTF arrives with its clips and its shapes, and one imported from FBX with its clips; both play back unchanged, and the engine container carries all of it. Skinning is a shader permutation rather than a switch, so a mesh with no rig carries none of its instructions. Retargeting moves a clip between skeletons that agree about their joints but not about their proportions, springs add secondary motion to hair and cloth without a simulation step, and a rig can be handed over to physics mid motion so a character that is animated one frame is ragdolling the next from the pose it was actually in. Because a clip is a function of a time the caller supplies, every one of those is reproducible: the same session replayed lands on the same frame of the same clip.

In the manual: Animation

Geometry

Procedural meshes, splines and swept surfaces.

Curves parameterised by arc length rather than by the parameter that happened to draw them, so something travelling at a constant speed along one actually travels at a constant speed, with frames that stay stable through a turn instead of flipping when the curve passes through vertical. Surfaces swept along those curves subdivide themselves where the curvature asks for it and stay coarse where it does not, so a long straight run costs what a long straight run should. Meshes are built procedurally from boxes and cylinders up, merged at build time so a whole static world can be a single draw, and collision geometry is generated from the same corners as the surface that is drawn, which is what makes it impossible for the two to disagree about where a wall is. A five by seven pixel font is built from bit masks when the module loads, so text on screen arrives without a file to ship, a request to make or a licence to clear.

In the manual: Meshes and geometry, Procedural solids, Splines, ribbons and lines, Instancing

Terrain

Ground that answers where it is, joins itself without a seam, and collides without triangles.

A heightfield that hands back the same vertex arrays every other mesh does, so ground is drawn by the pass already there and a world with none of it carries none of the cost. The query answers the surface that is actually drawn, which is the part most implementations quietly skip: interpolating smoothly between four samples describes a different surface from the two triangles spanning them, and the two agree only at the corners, so a character floats a little over half of every cell and sinks over the other half, everywhere, with nothing on screen to show it. Here the query reads the triangle that was built. Normals are taken as a difference across the samples instead, so shading stays continuous rather than faceting at every cell boundary. Where ground drawn at full detail meets a coarser neighbour, the finer edge is moved onto the coarse one so the two are a single line with nothing between them to fill: the usual answer is a vertical curtain dropped from every patch edge, lit unlike the ground it hangs from and paid for along every boundary for ever. Measured on both backends, an unmatched join shows better than fifteen thousand pixels of sky through the ground and a matched one shows exactly zero. Surfaces blend by height and by steepness, so rock arrives on a cliff and grass on a meadow without either being painted on, and a body collides against the field itself rather than against a mesh built from it.

In the manual: Terrain

2D

Sprites, sheets and tilemaps, and an interface tree that lays itself out and takes input.

A package rather than a verb on the renderer, which is what lets a project that never draws a sprite carry none of the sampler, pipeline and shader one needs. Sprites batch into runs rather than grouping by image: consecutive sprites sharing a sheet are one draw, and going back to an earlier sheet starts a third run instead of rejoining the first, because rejoining would reorder the picture and the order they were submitted in is the layering. A tilemap costs the view rather than the map, the visible span being arithmetic on four numbers, so a map of a million cells draws the twenty five tiles on screen. Two coordinate systems sit beside each other and count y opposite ways on purpose: screen space is pixels from the top left, and world space counts up, because somebody placing a platform above a floor should not have to subtract. The interface half is a tree measured from the leaves up and placed from the root down, in two passes and never a third, with three size cases rather than percentages, since a percentage of a parent sized by its contents is a question with no answer. It allocates nothing while it runs. Hit testing searches what was drawn last, so what answers a click is what a viewer can see, and a panel that is not interactive does not swallow the clicks meant for what is behind it. A press and a release must land on the same node, so somebody who pressed a button they did not mean can slide off it.

In the manual: Sprites and tilemaps, Interfaces, A 2D game

Audio

A layered music graph that answers to what the game is doing.

A mix that is a tree of buses with inserts, sends, solo, mute and snapshots, rather than a flat set of volumes. Music is layered stems brought in and out by an intensity a game supplies, under a master filter whose cutoff rides a speed the game names, so the score answers to what is happening without anybody scripting a transition. Effects are positional, with distance, occlusion, reverb zones and doppler, and a listener that can be a full head related transfer function where a game wants one. Sounds resolve through a registry: a named slot finds a real file when one exists and a synthesised approximation when it does not, so dropping a file into the assets folder is the whole of replacing a sound, with no code change and no registration step. Beat detection runs offline to produce a map and live against the same definition, so a recording can be cut in time with its own soundtrack and a scene can move on the beat while it plays.

In the manual: Sound and music

Input

Keyboard, mouse and touch, unified into intents.

Keyboard, a pointer that can be captured so a mouse turns a camera without ever reaching the edge of the screen, multitouch, and gamepads, all of it resolved into the intents a game acts on rather than left as raw events for every game to interpret again. A phone gets a stick that appears where the thumb lands rather than at a fixed corner, with tap, hold and swipe zones around it, and its size and dead zone are numbers a game chooses rather than constants baked in. Text fields are understood, so typing reaches the field instead of driving the player, and a window losing focus releases everything it was holding rather than leaving a key down indefinitely. Sampling reads the clock and the intents cross into the simulation as data, which is the boundary that lets a recorded session replay exactly: what is stored is what the player meant and at which tick, never what the hardware happened to report between two frames.

In the manual: Input, Moving things, Picking

Host

What a window, a phone and a store can do, asked as questions with honest answers.

A settings screen is built from this: window mode, surface size, the display list and its refresh rates, orientation and safe areas on a phone, file dialogs, and store services for cloud saves and achievements. The engine names no shell and takes these as parameters, so the same game runs inside one or in a tab. A browser answers false to what it genuinely cannot do rather than accepting the call and doing nothing, which is what lets a menu grey out a control instead of offering one that misleads. Because each of these is a parameter rather than a call, the same game runs in a browser tab, inside a packaged desktop application and on a native window with no browser anywhere in the process, and none of the three needs a build of its own. A shell that implements more of them offers more; one that implements none of them still runs the game.

Splats

Photographed places, drawn into the scene rather than instead of it.

A Gaussian splat capture is a real place, photographed and optimised into a few hundred thousand elliptical blobs. The engine reads them from the two formats the tools produce and draws them into your world, so a wall standing in front of a capture hides it and a light beam drawn after it passes through. Ordering is the picture here, because the blobs blend back to front and the right order changes whenever the view turns, so it is recomputed off the frame in a worker and only once the view has turned far enough to matter. A still camera does no work at all. A budget keeps whatever is largest on screen rather than truncating the order, which is what lets a weaker device draw a thinner version of the same place instead of a piece of it. Put a capture in the engine format and it streams: the file is laid out so the first fraction that arrives is the whole place at low density, and the rest fills it in. Colour is fixed rather than shifting with the viewer, so a polished surface in a capture reads flatter than it would in life.

In the manual: Gaussian splats

Core

A fixed timestep loop, seeded generation and pluggable storage.

The pieces a replayable game is built out of. Simulation advances only in fixed sixtieths of a second and drawing interpolates between the last two, so behaviour is identical on a ninety hertz phone and a hundred and forty four hertz monitor, and a frame that takes too long is caught up in whole steps rather than by one longer one. The seeded generator is frozen: the same seed gives the same sequence on every platform and every version, because changing it would invalidate every replay and every recorded run a player has kept, so a different distribution becomes a new function rather than an edit to an existing one. Saving goes through an interface a game supplies, so state can live in a browser, on a server, in a native shell or nowhere at all. Nothing in the runtime reaches for a clock or a random number on a path a game might simulate: time is a tick count the caller hands in.

In the manual: The loop, The frame, The scene graph, Determinism, Coordinates and units, Cameras and cinematics, Text and overlays

Network

Rewind and replay, with the two networking models built on one of them.

Prediction and rollback are the same operation, which is what this is built on. A peer that guessed what another player did and then learned the truth has to unwind the ticks it computed from the guess; a client whose authority disagrees has to unwind the ticks it computed from a world it has since been corrected about. Both are: put the state back to that tick, correct what the tick got told late, and step forward to now. So there is one rewind and the two models sit on it rather than each carrying its own. A snapshot preserves entity handles exactly rather than creating new ones, because an input recorded against something has to still mean that thing after the rewind, which is the difference between this and a save file. The transport is a seam a caller fills: a loopback whose latency, loss and reordering all come from a seed, a WebSocket, and a data channel that is neither ordered nor retransmitted, since either of those alone is half a fix. Relay, authority and participant are separate roles, so a service that relays for a session without simulating it is a thing that can be written. Nothing here draws, and the whole package is under three kilobytes for a game that imports it.

Entities

A world model where identity survives deletion and a system touches only what it declared.

An entity is a handle with a generation in it, so a handle to something deleted answers as stale instead of quietly resolving to whatever was created next. Components sit in typed columns and are queried without allocating anything per frame. A system declares what it reads and what it writes, and reaching past that is refused rather than tolerated, which is what turns a schedule into something the engine can reason about instead of an order somebody has to remember. Prefabs and whole scenes serialise, and a component is described once as a schema that both the storage and the editor read, so an inspector works for a component a script defined without being taught about it. Handles survive a snapshot and come back pointing at the same things, which is what lets a session be rewound and replayed rather than merely saved. The package imports nothing else in the engine, so the same world runs on a server where there is no renderer at all.

In the manual: Entities

Editor

An editor that opens, plays, scrubs a running game backwards and edits it while it runs.

The editor opens on a menu bar, a viewport and docked panels, with selection by picking, undo, redo and a command palette, and every change is one command on one undo stack, a gizmo drag included. Press play and then stop, and the scene is exactly as it was, checked by a digest of the whole world. Because the simulation is deterministic, a keyframe every few frames and the input log between them rebuild any frame, so the scrubber runs a session backwards and forwards again unchanged; an edit made at one frame is re-simulated from that frame, so scrubbing away and back keeps it; and when two runs stop agreeing, the editor finds the frame and then the component. One graph canvas serves three vocabularies: material graphs that compile to the decode programs a baked material is, particle graphs that compile to the pool options the engine already takes, and behaviour graphs that emit DriftScript for the language's own compiler to judge. Scripts are edited beside them against the language server. Its parts ship as packages, so a game that never opens a tool carries none of it.

XR

Headset sessions with the two eyes drawn from the runtime’s own matrices.

A session over whichever layer the browser offers: a WebGPU projection layer where the runtime has one, and the WebGL layer every implementation has had since the first, chosen from what the session actually provides rather than from a preference. The camera does not describe an eye, it adopts one, and the reason is arithmetic: a headset lens sits off the centre of its display, so the frustum is sheared, and a symmetric projection cannot express that whatever field of view it is given. The runtime hands over a matrix per eye and the camera takes it, recovering where the eye is and which way it looks from the matrix itself, so audio placed at the camera follows the head. The loop follows the session rather than the page, because a headset runs on its own clock and drawing at the browser’s rate into a display that wanted another makes people ill. Controllers are read by the names the standard profile fixes rather than by an index, and a hand arrives as twenty five joints. A joint the cameras lose keeps the pose it had, because fingers occlude each other constantly and collapsing one onto the wrist for a frame reads as a twitch; a flag says the pose is old and what to do about it stays with the game. Everything that can refuse says why in a sentence, because a person whose button did nothing is owed the difference between a browser without the feature, a machine without a headset, and a context the runtime would not accept.

Intelligence

Agents with a deterministic floor, typed tools, and decisions that survive the trip.

Provider-neutral sessions with budgets and capability policy in code. Underneath every agent is a deterministic floor that runs on the fixed step and knows no provider exists, so an agent whose model is slow, absent or over budget loses quality rather than motion. A model never writes an execution path: a tool resolves by id to something registered beforehand, its arguments are checked against a schema, and a decision whose world has moved on is discarded rather than run late. The navigation bridge makes that rule real: a route is searched at the moment the agent acts, so a destination that stopped being reachable while the request was in flight is dropped and the floor covers. Across a session, a model is a thing one machine does: it is slow, it varies and it is metered, so two peers running the same loop drift apart. Decisions are taken on the authority and reach everyone else as facts they replay, and a replay reaches no provider at all, which is what makes a rewind free and a recorded run exact. A script can route an agent, ask whether a place is still reachable, read the route it is following and ask whether this machine is the one deciding, and it can read none of what the model chose, because a function that branched on a provider answer would take the other branch the moment it was replayed.

In the manual: Agents and behaviour

Scripting

A strict language for behaviour, with state that survives a reload.

DriftScript compiles to JavaScript modules inside your bundler and loads through a runtime of a few kilobytes, so what ships is ordinary code rather than an interpreter. It is strict where a scripting language usually is not: absence is a type you have to handle rather than a value that turns up later, arithmetic that overflows says which kind of overflow it was, units are part of the type so a metre and a second cannot be added, and effects are declared, which means a function marked deterministic is refused at compile time if it reaches for a clock. A capability the host does not provide is refused at link time by name, so a script written against a subsystem the target lacks says so before it runs rather than while somebody is playing. The engine describes itself to the language as data, which keeps the two independent, and a project mixes scripts and hand written code freely. Editing a function replaces it while the state it was working on stays alive.

Where it runs

One build, opened as a page or installed as an application. The web is the target everything else is a shell around, so reaching a store does not turn a product into two codebases.

Web

The first target and the one with nothing standing in front of it. A link opens the running thing, with no install ahead of it and no review queue behind it.

Windows

An installer wrapping a bundled Chromium, so a player runs the renderer this was tested against instead of whichever browser their machine happens to carry.

macOS

An application bundle that carries a signature every time, because Apple Silicon declines to start a binary with none at all. Cross-built from Linux as well as made on a Mac, so shipping to one does not require owning one. The disk image still does.

Linux

An AppImage and a tarball, measured running a real game with WebGPU drawing through it and clip export intact, which is the pairing that took the longest to hold on this platform.

Android

A signed package around the system WebView, served from an origin that makes it a secure context, with the back gesture reaching the game before it closes anything and a download landing in the place a phone keeps them.

iOSahead

A native view holding the same containment rule the desktop shell has, with everything the engine reads at startup injected ahead of the first script, because that platform hands nothing back to a page synchronously. Written against Apple documented behaviour and waiting on the one thing a script cannot fetch, which is a Mac with Xcode on it to compile the first build.

Steam

Cloud saves and achievements arrive through seams the engine already had, so a game reaches them by naming an app id rather than by importing a store. The depot files are written for the uploader with nothing set live.

Consolesahead

The direction rather than a destination. Drawing already sits behind one surface with the backend chosen underneath it, so a native one is a shape this has taken before. It starts with a platform agreement.

One requirement

WebGL2, which every current browser has had for years. There is nothing to install, nothing to submit for review, and one build rather than a separate mobile version to keep in step. WebGPU is what the engine reaches for first now, and a browser that will not give a working device falls back to WebGL2 with the same scene and the same build, so the floor is unchanged by the newer path sitting above it.

Desktop

Linux, macOS and Windows, on integrated graphics as readily as on a discrete card. Developed and profiled on Linux against a real GPU rather than a software renderer, which is where the awkward driver behaviour actually lives.

Phones and tablets

Android and iOS, from budget handsets on Adreno parts up to current flagships. The frame budget was set against the low end deliberately, because an engine that only holds its rate on good hardware has not been finished.

The same picture everywhere

Quality tiers move resolution, shadow detail and effect density rather than turning features off, so a cheap phone renders the same world at a cost it can carry instead of a reduced version of it.

Inside a budget somebody else set

A lot of the 3D on the web has to fit a hard ceiling rather than a target: a network that refuses anything over a few megabytes, a product page that has to be interactive before a shopper gives up, an embed whose host decided the limit. This engine has had a payload budget since its first week, so a runtime and a generated world together sit inside limits that are usually the reason 3D gets cut from a brief.

Getting it

The engine is on npm, under Apache 2.0, and the source is on GitHub.

npm i @driftengine/core

That is the renderer, the scene graph, the fixed-step loop and the maths, which is everything a first scene needs. It brings its own dependency on the physics package and nothing else.

Twenty four packages, and a project carries what it draws

One subject per package, each installable on its own, each with a barrel that is the whole of its public surface. A game with characters takes the animation package and a game without one leaves it; a tool that makes sound takes the audio package, and a visualiser that draws in silence takes neither of them and pays for neither. That is where the payload figures above come from: a project carries the subjects it draws. Packages depend on each other where the subject really does depend on the other one, and npm brings those along.

  • @driftengine/aiProvider-neutral intelligence sessions, typed tools and context, and an agent loop that does not wait
  • @driftengine/animationSkeletons, clips, poses and the graphs over them: sampling as a pure function of a caller-supplied time
  • @driftengine/assetsModel readers and the streaming loader for the .drft container
  • @driftengine/audioLayered stems, synthesis, rhythm analysis and positional placement
  • @driftengine/captureA video to a playable scene, on the player's device: the models it runs and the stages that use them
  • @driftengine/chemistryThermochemistry of bulk matter: elements, species, substances and what they turn into
  • @driftengine/coreThe runtime of DriftEngine, a WebGPU game engine with a WebGL2 fallback: renderer, loop, input, cameras, scene graph and physics
  • @driftengine/createStarts a DriftEngine game: a Vite and TypeScript project, its instructions for a coding agent, and the engine's skill
  • @driftengine/drftThe .drft container: a streaming format for baked 3D scenes
  • @driftengine/editorThe model of a scene editor: a tree of what exists, an inspector over what is selected, and play-in-editor
  • @driftengine/entitiesEntities with generational identity, component storage, systems and scenes
  • @driftengine/mediaClip encoding and frame delivery for recording what the engine draws
  • @driftengine/native-hostThe engine on a native window and a native WebGPU device, with no browser between them
  • @driftengine/navA navigation mesh: geometry to walkable convex polygons, and a straight line across them
  • @driftengine/networkA transport seam, rewind and replay, lockstep and an authoritative host, on the fixed-step loop
  • @driftengine/packageBuilds an installable application from a game built on the engine
  • @driftengine/physicsCollision shapes, spatial queries and swept kinematic collision
  • @driftengine/scriptThe drift/* capability bindings: what this engine provides to DriftScript, and the only place the two are coupled
  • @driftengine/splatsGaussian splat captures: readers, an off-frame sort, and a pass that composes into the scene
  • @driftengine/terrainHeightfields: a query that answers the surface that is drawn, and patches that meet without cracks
  • @driftengine/textureDriftTexture: a texture as a compiled, sampled field rather than an image
  • @driftengine/toolsThe editor panels a shipped game can carry, and the command stack that makes their edits undoable
  • @driftengine/ui2dThe 2D layer: batched sprites, sheets, tilemaps, and a retained interface tree over them
  • @driftengine/xrWebXR sessions, stereo views, controller input and hand joints, over both backends

Every symbol in every package is in the reference, generated from the barrels at build time. Read it.

Where to start after the install

The repository carries fifty five small runnable programs, one capability each, from a first lit scene to a complete 3D game. Between them they are the shortest path from an empty file to something on screen, and each is short enough to read in one sitting. The scenes on this site are a different thing: they are the engine demonstrating itself, they live in the repository rather than in any package, and installing the packages does not bring them.

Apache 2.0

Free to use, including commercially, including in something you sell. What the licence asks for is what every Apache 2.0 project asks for: keep the copyright and attribution notices, say what you changed, and pass the NOTICE file on with anything you distribute. It is free, whatever a project earns and however many people work on it.

The splash screen, which is a request

The engine draws a short DriftEngine splash on start and it is on by default. Turning it off is a setting and always will be, because a licence that forced it would not be Apache 2.0 any more. Leaving it on is how a project this engine cost nothing to build on says where it came from, and it is the only thing asked for beyond the licence.

With a coding agent

Built to be written by a coding agent as well as by a person.

npm create @driftengine@latest my-game

That writes a project which runs as it stands, with what an agent needs to keep it running: the instructions most agents read when they open a folder, the engine's skill, and one command that says whether a change worked. A game's rules are tested in Node with no GPU, because the simulation is deterministic and drawing never writes back to it, so a rule that holds in a test holds in the game.

A starter that asks nothing

Every choice has a default and a flag, so an agent can run the command unattended. It writes a Vite and TypeScript project, an AGENTS.md saying what done means there, and the script that looks at the game. A second template starts from a complete 3D game with physics, a character, sound, shadows and a HUD.

A skill at the installed version

The skill teaches an agent the shape of a program, the rules a compiler cannot check, and where every page of the manual and every example is, at the version the project installed. It is written where Claude Code, Codex and Cursor look for skills, and one command brings it up to date after an upgrade.

A check that looks at the game

One command, npm run check, typechecks, runs the tests, then opens the game on WebGPU and on WebGL2 in Chrome or Edge with the machine's own GPU, on Windows, macOS and Linux. It keeps a picture from each, says in words what it saw, and stops at a console error or warning, a canvas of one flat colour, or a backend other than the one it asked for.

What using it looks like

Quality is decided once, not per frame

Allocation sized choices are construction arguments, so nothing on a hot path branches on a setting. The factory reports which backend it got, because a browser that cannot give you the newer one falls back quietly and draws a frame that looks fine.

const { renderer, backend } = await createRenderer(canvas, {
  maxDevicePixelRatio: 2,
  directionalShadows: true,
  pointShadows: true,
  water: true,
  waterReflections: true,
});

Simulation and rendering are different clocks

The loop steps the world at a fixed rate and hands render an interpolation factor. Render never writes back, which is what lets one machine run at 144 Hz and agree with another at 60.

startLoop({
  fixedStep: 1 / 60,
  update(dt) {
    world.step(dt, intents);
  },
  render(alpha) {
    renderer.beginFrame(sky);
    renderer.drawMesh(mesh, world.poseAt(alpha));
    renderer.endFrame();
  },
});

The same seed is the same world, anywhere

Integer mixing rather than a trigonometric hash, so it is bit identical on every platform and stable across versions. A replay stored last year still plays back.

const rand = mulberry32(seed);
const palette = pickBySeed(PALETTES, seed);

// Same seed, same sequence, on any machine and any build.
const gust = rand() * 2 - 1;

A language for behaviour

Behaviour is a language here, and it is not this one.

DriftScript is a strict language for the code that decides what a world does. It compiles to JavaScript modules at your bundler and loads through a small runtime, so saving a file replaces the running function while the state it was working on stays where it is. It depends on no engine package and carries its own version line, because an engine release must never quietly redefine what a language means.

Strict where it counts

Absence is a type rather than a null. A condition is a bool. Overflow says whether it checks, wraps or saturates, and widening is something you write rather than something that happens.

Capabilities are declared, and refused by name

A script imports what it needs and a target declares what it provides. A module the target lacks is refused at link time, naming the module and the target, so a file can be written against a capability before anything provides it and link unchanged on the day one does.

This engine is its first host

Everything the engine contributes arrives through one package of bindings, where a capability is described as data and the host supplies the functions at link time. A different host would supply its own prefix and inherit the standard library unchanged.

Read the language

Frames

How a frame is made

Input

per frame

Devices are sampled and become intents: plain values carrying no notion of what a game does with them.

keyboard, pointer, touchthe only stage that reads a clock

Simulation

fixed 1/60

The world advances in fixed steps rather than wall clock time. Same intents in, same state out, on every machine.

seeded generationrigid bodies and contactsauthority lives here

Interpolation

per frame

Render reads a blend of the last two states and never writes back, so the picture runs at any rate the display offers.

120 Hz and 60 Hz agreeno state written

Shadow passes

budgeted

Sun and moon cast into layers that persist while nothing moves. Point lights own cube maps drawn a few faces a frame.

static layers persistcube faces rationed

Scene pass

one target

Flat shading from vertex colour, then water and its reflection, light in the air, fire and smoke, and sky behind what is left. The scene can keep its real brightness to the end of the frame rather than clipping it surface by surface, and is graded here.

static world mergedgraded at this frame’s exposureno image required

Composite

off screen

One resolve over the off screen target: occlusion from depth, a spread of whatever is brighter than the threshold, camera blur from the matrix the frame was drawn with, speed blur on request, a defocus that follows a focal distance given in metres, exposure that adapts over time and region by region, a lens's darkened corners, a print's grain, and the look a grading table describes.

each pass off until asked forskipped when nothing wants it

Present

to the canvas

The image reaches the canvas. A readout or an inset drawn after it lands on top, untouched by any of that.

the interface stays sharpready to record

How it stays fast

Nothing allocates while a frame is running

Scratch vectors, matrices and accumulators are claimed once at module scope and mutated in place. Results drain into objects the caller owns. Closures, arrays and string building are all kept out of the per frame path. The collector has nothing of ours to collect, so frame times have no sawtooth in them.

The static world is one submission

Geometry that never moves is merged ahead of time into a single buffer, so a whole level is a handful of draw calls rather than one per object. Instanced meshes cover the rest, with per instance data packed into a typed array and uploaded as one range.

Shadows are rationed, not skipped

The sun draws into layers that persist while nothing moves, so a static world pays once. Point lights own cube maps refreshed a few faces per frame against a fixed budget, and a light that has not moved keeps its image indefinitely. A room full of lamps costs a slice of a frame instead of all of it.

A light nobody can see does not cost anything

Sources are selected per frame by distance and by view, arriving and leaving on a ramp rather than a switch so nothing pops. Anything past its range, or behind you, releases its slot to a light that is actually visible.

Resolution gives way before the frame rate does

A governor watches frame times and moves the drawing buffer scale, dropping quickly when a machine is struggling and raising slowly behind several good windows. It exists for hardware nobody here owns, including hardware that does not exist yet.

A texture fetch never hides in a branch

Sampling inside control flow a compiler cannot prove uniform lets it evaluate every arm and discard the ones not taken. One shader carried that shape and cost 13 ms a frame on a backend that flattened it. Explicit level fetches are a rule here, not a preference.

Model formats, and a container that streams

The engine reads a model directly from the formats a studio already has, and it ships one of its own for the things it keeps. Both matter, and they are not the same job: one is for getting a model in, the other is for shipping it.

  • glTF 2.0

    .glb, .gltfOpen standard

    The Khronos standard, and the one to prefer where a bundle offers it. Geometry, materials, the node graph flattened to world space, and normals through the inverse transpose.

  • Wavefront OBJ

    .obj, .mtlOpen standard

    Geometry, UVs and materials from the .mtl beside it. No transforms to misread, which is why it is often the most faithful file in a bundle.

  • STL

    .stlOpen standard

    Triangles and a facet normal, which is all the format carries. It states no axis convention and most are Z-up, so the tool says so rather than guessing.

  • USD

    .usdz, .usdaOpen standard

    Geometry, materials, transforms and the unit scale the file declares. The binary crate is refused by name rather than half-read.

  • 3MF

    .3mfOpen standard

    Geometry and colour, Z-up by specification. The core format carries no UVs and the table says so rather than pretending.

  • FBX

    .fbxExperimental

    Geometry, materials, UVs, vertex colours and embedded textures, the unit it was authored in, and the rig where a file carries one: the skeleton, the weights binding a mesh to it, and the takes that move it. This is the format characters are sold in, so a figure bought from a store arrives ready to pose, at the size its author drew it. A best effort rather than a promise: it is a proprietary container, and support may stop working when the vendor changes it.

  • Maya binary, 3ds Max

    .mb, .maxRecognised

    Identified by their magic bytes and refused with the export that works named, because a renamed file deserves a reader and a proprietary binary deserves an instruction rather than an obscure parse error.

A container that streams a large model progressively

Anything you mean to keep gets converted once into the engine’s own container. That is what buys a large model arriving progressively over an ordinary fetch, refining as its bytes land, with nothing asked of the server beyond serving a file.

Zero-copy load

Vertex and index data sit in the file at 4-byte alignment, so the loader builds a typed array straight over the bytes it fetched. Nothing is parsed, copied or translated per attribute on the way in. It is the whole reason the format exists.

Old files always open

A file written today opens in every future reader, with no expiry and no migration step. Unknown optional chunks are skipped in silence, which is what makes additive change free; a chunk marked required and not understood is a refusal rather than a partial asset.

One fetch, and it cannot half-load

Textures are embedded, so a model is a single file. The chunk table is a manifest sitting in the first few kilobytes, so a reader knows the entire layout of a 40 MB file before the geometry arrives.

It arrives in a useful order

The payloads are laid out the way a viewer needs them: an outline if one was written, then the materials, then the meshes, then the images. A plain sequential fetch therefore refines the model as bytes land, with no range requests and no server support beyond serving a file.

It can open on an outline

A file may carry a coarse version of the whole model, written ahead of the geometry it stands for. On a 76 MB car that is 140 KB, so a recognisable shape is on screen inside the first per cent of the download and the parts land on top of it. Off unless you ask for it at both ends, because a coarse version is a decimation and how good it looks belongs to the model rather than to us.

Every image keeps its name

A texture is stored with whatever its source called it, so replacing one at runtime is a question you ask by name rather than a position you count to. The new image uploads into the GPU object the draw loop already holds, so nothing downstream is rebuilt, and a name the file does not carry raises at the call with the list of names it does. An index that has gone stale still resolves, silently, to the surface next to the one you meant.

The container in detail: how the bytes are laid out, and the code that reads one

DriftEngine and Drift Technologies are not affiliated with, endorsed by or partnered with the Blender Foundation, Autodesk, Inc., Pixar, the Khronos Group or any other vendor named here. Blender is a trademark of the Blender Foundation; 3ds Max, Maya and FBX are trademarks of Autodesk, Inc. Those names are used only to describe which programs’ exports this engine can read. Support for any third-party format is provided on a best-effort basis, is not guaranteed, and may stop working when the vendor changes their software.

What the tests certify

8406 tests run on every change, and the number on this page is the engine's own record of its last run.

A test protects a contract, never a number

Tuning values are decisions and change on every pass, so asserting them produces tests that go red on intent while letting real defects through. What is asserted instead: a body never ends inside a solid, auto step climbs what it should and refuses what it should not, angles wrap correctly across the turn, and the loop accumulates time exactly.

The random sequence is a test, not a hope

Determinism is what makes a replay worth storing, so the generator has its own assertions. A change to it would invalidate every run anybody has saved, which means a different distribution has to be a new function rather than an edit to an existing one.

A fix starts by reproducing it

Every fix begins with a test that reproduces the behaviour, and it is watched going red before anything is changed. A test written afterwards proves the code does what it now does, which is a different and much weaker claim than proving it does what it should.

Expected values are worked out by hand

Never produced by the code under test, and generators may supply inputs but never expectations. A test that computes what it checks agrees with itself however mistaken both halves are.

The public surface is checked against itself

The reference on this site is generated from the barrel at build time and verified before it publishes, so a signature here cannot drift from the engine. The build fails rather than shipping a page that is quietly out of date.

A shader is looked at, not assumed

A suite of assertions cannot see a picture, so the tools that photograph a frame and compare two runs of it ship with the engine. They refuse to measure at all on a software renderer, because an image drawn without a real graphics card agrees with itself and with nothing anybody will actually run on.

Two backends check each other

The same scene, the same frame, drawn through both paths and compared. Neither path has to be taken on trust on its own, because anything the two disagree about shows the moment they are put side by side, without anybody having thought to look for it in advance. That comparison runs over every scene the engine ships with.

What it gives up

Nothing is required to be an image

A world that is a few hundred kilobytes instead of a few hundred megabytes. Colour comes from vertex data and procedural shaders, so a scene has nothing to download, decode or cache. Bring images when a model needs them and the importer embeds them in the same file.

A hierarchy that does not draw itself

Draw calls you can count. There is a transform hierarchy, and walking it hands you nodes rather than issuing anything, so the submissions stay yours: geometry that never moves is merged ahead of time and a whole world goes out in a handful of them. That is why it holds a frame on a phone.

No allocation in a running frame

A frame time that does not spike. Scratch state is claimed once and mutated in place, so the collector has nothing of ours to collect.

A frozen random sequence

Replays that survive an upgrade. A stored run from a year ago plays back the same, because a different distribution is a new function rather than an edit to an old one.

A surface that is now a promise

Everything a published barrel gives: a name that stays a name, a version that means something, and a reference generated from the source rather than written beside it. What it costs is speed. Every version until publication moved something a public promise would have frozen, and the shapes that were still moving are frozen now, so a change that would once have been an afternoon is a major version and an argument.

Why build another one

Determinism is not something you add later

A world that replays exactly needs a fixed step, a frozen random sequence and a strict rule about which code may decide anything. Those are choices made in the first week or not at all, because retrofitting them means auditing every system that ever read a clock.

A frame budget is a habit, not a setting

Holding a steady frame on a phone comes from thousands of small decisions about allocation and draw submission. An engine that did not start there can be optimised, and it cannot easily be made to stop allocating, because the allocations are in its shape.

The pieces had to know about each other

Music that follows the run, a replay that matches the music, and a recording that matches both. Assembling that from separate libraries means three clocks that agree only by accident, where one engine has a single tick everything hangs from.

Small was the point

A game that loads in seconds on a phone rules out most of what a general purpose runtime carries. Starting narrow and staying narrow is how the payload budget survived contact with a real game.

Where it is going

The rules that do not move

Everything below arrives inside the payload budget and the frame budget, or it does not arrive. Determinism is a contract rather than a feature, so nothing new is allowed to break a stored replay.

Run where it was written for

Two things are written in full and have not yet met the hardware they are for. WebXR has sessions over both backends, stereo views, controllers and hand joints, checked against a synthetic runtime, and has not drawn a frame on a headset. The iOS app has its host, its scheme handler and its build step, and waits for the first machine that can compile it.

More of the toolkit

Reflections of the frame itself on the GPU driven path, which reflects a captured probe or a gradient, so a wet street cannot yet give back the lights above it. And a frame built from the two either side of it, beside the learned half of the reconstruction that ships here in its analytic form. Each has to earn its place against the same two constraints as everything else.

A surface that keeps its word

The packages are published and the barrels are the promise, so a name that is there is a name that stays there. What that costs is deliberate: a breaking change is a major version and an argued one, an addition is preferred to a change wherever the two would do, and the reference on this site is generated from the barrels so it cannot describe a surface the engine does not have.

What it costs

Version
4.13.5Shipping continuously.
Tests
8406Covering the contracts a game depends on.
Public API
2613 symbolsThe full surface available to your game.
Payload
under 10 MB gzippedA whole world, on a mobile connection.
Draw calls
2,000 to 10,000 at 60 fpsFar past that where the draws are instanced.
Frame rate
60 fps on a mid range Android phoneMeasured on real devices, not estimated.

Moving

4.13.5
The haze takes a second layer, a most it may hide, a distance past which it hides nothing, and the sun glowing in it: `fogLayer2`, `fogMaxOpacity`, `fogCutoff` and `fogSunGlow`.6 changes · 2026-10-12
4.13.4
Hair takes the sky, a probe's light and DriftLight through its own highlights, for the eye's side of each strand, and reflects none of the environment, so a fibre keeps its colour in shade.2 changes · 2026-10-12
4.13.3
A material can lay fine grain over its maps, a region at a time: up to sixteen detail normals and occlusions at their own repeats, chosen per texel: `SurfaceMaterial.detail`.2026-10-12

Every release

Get in touch

Join the Discord Ask questions, share what you are building, and talk with other developers.

info@driftengine.dev