DEV Community

Cover image for Flare Engine is open source: six months, two good decisions and two bad ones
Grzegorz Otto
Grzegorz Otto

Posted on Originally published at grzegorzotto.dev

Flare Engine is open source: six months, two good decisions and two bad ones

Originally published at grzegorzotto.dev. Translated from Polish with AI; the Polish original is my own writing.

Flare Engine has been open source since October 26. It is a game and animation engine for TypeScript and React Native: 42 packages on npm, the Apache-2.0 license, and one command to a first project. Below I tell where it came from, where it already runs, which decisions paid off and which did not.

Where it came from

Games have been part of my everyday life since I was very young, and the dream of making one of my own never left me. My core stack is TypeScript and React Native, together with Bun, and I genuinely like that ecosystem.

Day to day I built RN apps. After hours and on weekends I made small games and tested how to build a game in different technologies, RN included. That is where the engine came from. At first I used it only as a tool for my own projects, experimenting with it after work. Over time I noticed that the React Native world lacks anything like it.

What is available

All 42 @flare-engine/* packages are public on npm at version 0.4.5, under the Apache-2.0 license. You do not need a token: a plain npm install works for everyone.

The fastest way to start is the project generator:

npm create @flare-engine my-game
Enter fullscreen mode Exit fullscreen mode

There are three templates to choose from: blank, topdown and shmup (-- --template <name>). You do not have to take the whole thing, though. The engine is cut into small packages and you install each one separately: physics, animation, particles, pathfinding, save state. You take only what you need.

Here is a single package in action. This is the example from the README of @flare-engine/dynamics, the rigid-body physics: a floor, a box and one simulation step.

import { addBody, createBody, createWorld, step } from '@flare-engine/dynamics';

const world = createWorld({ gravity: { x: 0, y: 1800 } });

// Static floor: invMass 0, never moves.
addBody(
  world,
  createBody({ position: { x: 0, y: 100 }, half: { x: 500, y: 10 }, static: true }),
);

const box = addBody(
  world,
  createBody({ position: { x: 0, y: -50 }, half: { x: 10, y: 10 } }),
);

// Fixed-timestep loop.
step(world, 1 / 60);
console.log(box.position.y, box.velocity.y);
Enter fullscreen mode Exit fullscreen mode

No React, no canvas, no framework around it. Plain functions on plain objects, so the same code runs in an RN app, in the browser and in a unit test. The example is not decoration either: the engine's CI typechecks every README example against the real code, so if this one ever stops compiling, the build goes red.

Where you can already see it

The best place is flareengine.dev/showcases. Everything runs in the browser, with nothing to install, and each showcase names the package that drives it.

Playable demos:

  • Overrun (ecs): sixty seconds in the arena. The chasers turn at a fixed rate, so a hard corner makes them overshoot.
  • Causeway (render-web): a road that runs out. Seeded stages, three lives, and every gap is one the ship can actually clear.
  • Reroute (pathfinding): paint walls to reroute, mud to slow, towers to kill. Ten waves against your core.

Animations:

  • Logo reveal (postfx): the logo on a loop, lit by a hand-written shader.
  • Nuclear reactor (ecs): a chain reaction on real reactor kinetics.
  • Physarum network (simulation): a slime mold network that finds the shortest paths on its own.
  • Procedural map (pathfinding): seeded fantasy cartography, drawn a tile at a time.
  • Spacetime lattice (simulation): an N-body merger, with the warped spacetime drawn as a lattice.

My game, Pan Tvardowski, is also being built on the engine. That is where I test it in practice. There is a small example on grzegorzotto.dev too: the Collapse button on the home page brings the page down on its real elements, and the physics is the same @flare-engine/dynamics as in the example above.

Not just games

The engine is not only for games. Some of its packages are useful in an ordinary app you want to bring to life a little:

  • animation: tweens, springs and timelines,
  • particles: particle effects,
  • progression: upgrade tracks, drop tables and an unlock graph,
  • achievements and leaderboards: achievements and rankings,
  • haptics: vibration, in the browser too.

To be honest: achievements and leaderboards currently ship an interface and local providers, for example in memory. Adapters for Game Center, Google Play Games or Steam are planned for later, and until then you plug in your own backend at the edge of your app.

Half a year that felt like five minutes

The engine's first commit is from 2026-04-17. At the end of April the engine had 36 modules; today it has 42.

During the same time I was building a team game in C++ and Unreal Engine, where I am responsible for the C++ core. That experience gave me a much better grasp of which elements are key to a game engine, which modules it should contain and which features are worth adding.

How this is made

Here is how the work gets made: coding agents write the implementation, while the architecture, the decisions, the verification and the review are mine. That matters for the two decisions below, because both are about how to keep such work in check at all.

Two good decisions

Modules that depend only downward

I split the engine into separate modules arranged in layers. At the bottom sit math and events, above them core and ecs, then rendering, physics, audio and the rest of the systems, and at the very top the integrations: react, render-web and the project generator. A module may only reach downward, never upward.

Layer 4    react, render-web, engine, create, cli
   v
Layer 3.5  ui, postfx, achievements, leaderboards, ...
   v
Layer 3    scene, animation, particles, tilemap, ...
   v
Layer 2.5  storage, errors, haptics, share, ...
   v
Layer 2    render, physics, dynamics, audio, input, ...
   v
Layer 1    core, ecs
   v
Layer 0    math, events
Enter fullscreen mode Exit fullscreen mode

An arrow means "may depend on". The other direction is not allowed.

Since July this has been guarded by the script scripts/check-deps-tiers.mjs. In CI it checks three things: whether a package declares a dependency it does not use, whether it uses something it did not declare, and whether it imports from a higher layer. One violation and the build is red. It saved me from dependency hell across 42 packages.

Skia as the renderer

Skia works in both places I care about: in React Native through react-native-skia, and in the browser through CanvasKit, which is Skia compiled to WebAssembly. One renderer for both platforms is, above all, a time saver.

On top of that there is performance headroom. The engine's web benchmark of 2026-10-20 (engine 0.4.1, commit 785a3b2): Pixel 7, Chrome, Mali-G710 GPU, render.flush for 5,000 sprites in one atlas is 1.93 ms p95 (n=100) against a frame budget of 16.67 ms. That is a CPU-side cost, meaning building and submitting the frame on the main thread, not GPU execution time.

Two bad decisions

Hardcoding 32 components

This is the first thing to fix. The engine's ECS gives every component type one bit in a mask, and queries match entities by exactly that mask. Here is how it looks in code (an excerpt from packages/ecs/src/component.ts):

let nextComponentId = 0;

export function defineComponent<T = undefined>(name: string, defaults?: () => T): ComponentDef<T> {
  const id = nextComponentId++;
  if (id >= 32) {
    throw new Error(`Maximum 32 component types exceeded (defining "${name}")`);
  }
  return {
    id,
    bit: 1 << id,
    name,
    create: defaults ?? (() => undefined as T),
  };
}
Enter fullscreen mode Exit fullscreen mode

Bitwise operations in JavaScript work on 32-bit integers, so the mask ends at 32 types. On top of that, the nextComponentId counter is shared by the whole process, not by a single world (World). This is a blocker: you cannot build a large, complex game on this engine today, even though the renderer allows it.

This release brought the limit out in practice. The main bun test in the engine repo crashed on the shmup template with the error Maximum 32 component types exceeded, because the runner loads all packages into one process and the counter is global. Run on its own, the template passes 14/14. That is a runner artifact, not a big game, but it is the same limit.

Relying on a soft harness

At the start I relied only on rules described to the agents in text. A soft harness does not guarantee correct output. The first two months were hell: neither the models nor my setup were good enough back then for me to work as effectively as I do today.

You can see it best in the documentation. The code examples in the README looked fine, but nothing compiled them. When I checked in July, it turned out they failed to compile in roughly 24 of 41 packages: wrong import paths, renamed exports, arguments of the wrong shape. Since 2026-07-12 the check:readme script typechecks every README example against the real code. That is why the dynamics example at the top of this post is guaranteed to compile.

Over time I understood that you cannot build a proper engine without written-down rules and without hard harnesses that can go red. Today the engine repo has separate gates for dependencies between packages, exports, the contents of npm packages, publish safety, performance and the examples in the documentation.

What I am proud of

I am glad I can take part in the TypeScript and React Native ecosystem, and I hope my engine will support its growth.

I kept the deadline despite many variables in my private life: leaving a salaried job, starting my open source adventure and founding a company from January 2027. I promised October 2026 and it is October 2026.

I am also proud of my growth as a programmer, because today I fully understand how a game engine works.

In short

I built an engine for the RN and TypeScript ecosystem that makes it easier to make a game or an animation and embed it on your website or in an RN app. Isn't that wonderful?

If you want to try it, start with npm create @flare-engine my-game, or take a single package like @flare-engine/dynamics. A star on GitHub helps the project, questions and ideas go to GitHub Discussions, and bugs go to Issues. The first thing I am fixing is the 32-component limit. I would love to see what you build.

Top comments (0)