Skip to content

Tech Research

Background for the tech stack, gathered on November 1, 2026. This is research, not decisions: the decisions are in player-and-platform.md and decision-log.md. Numbers here are approximate and dated; check them again before relying on them.

What this game needs from a stack

  • The simulation doesn’t need an engine. It’s rules over data with no physics or animation, and it must run on a server, in the browser for single player, and headless for tests.
  • Engine networking helps less than usual. Engine netcode targets short matches with a few dozen objects. This game needs a huge persistent world, fog filtering and plans instead of positions, so most of the sync layer would be custom on any engine.
  • It’s a panel-heavy game on a flat grid, so a good user interface toolkit matters more than a 3D renderer.
  • Most code will be written by Claude, so text-only projects, a fast run loop and widely used, stable APIs matter.

Options compared

Web: TypeScriptGodotUnitySwift
LanguageTypeScript everywhereGDScript or C#C#Swift
RenderingPixiJS (2D) or PhaserBuilt in, strong 2DBuilt in, 2D and 3DSpriteKit or Metal
Panels and overlaysHTML over the canvasGood built-in UIWorkableSwiftUI
PlatformsAny browser; wrap for app storesAll, including webAllApple only
Coding with ClaudeBest: all text, fast loop, Claude can view itGood: scenes are text; Godot 3 and 4 APIs get mixed upWeaker: much setup happens in the editorGood code, but few game resources
Main riskPerformance ceiling late gameSmaller ecosystem; C# projects couldn’t export to web in Godot 4 when last checkedEditor-heavy; 2023 pricing scare (cancelled)Locks out PCs and Android

Also considered: Unreal (far more than a 2D grid game needs, and visual scripting isn’t text) and Rust with Bevy (below).

Factorio, for reference

  • Written in C++ on its own engine. It started on the Allegro library and later replaced it with its own rendering layer. Lua is used for mods and scenarios.
  • Deterministic lockstep at 60 ticks per second: every player’s computer runs the whole simulation. It shows the player’s own actions at once and calls this latency hiding.
  • 2D, with art pre-rendered from 3D models at a fixed angle, which is the plan for this game’s later art.
  • It went custom because its identity is enormous scale, millions of items on belts. Belts are also why it uses lockstep. This game removed belts, which is why a server-owned tick and an off-the-shelf stack fit.

Replication frameworks

Nobody writes networking per feature. Games use a generic replication layer: declared synced fields, change tracking, per-client filtering, compact encoding, acks and snapshots.

  • Engine built-ins: Unreal’s property replication, Unity’s Netcode for GameObjects (and Mirror, FishNet, Photon Fusion), Godot’s MultiplayerSynchronizer.
  • Colyseus (TypeScript, Node): rooms plus a state library with binary diffs and per-client filtering. Not used for the prototype, because single player runs the host in a browser worker.
  • SpacetimeDB: the server is a database; clients subscribe to queries and get row-level diffs. Newer and less proven.
  • Custom: common at large studios, still one generic layer.

Comparing every field of every object each update gets expensive with thousands of idle buildings; Unreal added its Replication Graph and later the Iris system partly for this. Prefer marking fields as changed when they’re written.

Bevy, as of November 2026

State of the project

  • Current release 0.20 (October 8, 2026): 227 contributors, 817 merged changes. About three releases a year (0.18 in January, 0.19 in June).
  • Fixed-timestep schedule, runs headless for servers, runs in the browser through WebAssembly, and can compile natively for iOS.
  • Lightyear is the main networking library: server-authoritative, uses Replicon for replication and interest management, works in the browser over WebTransport or WebSockets. Its current version targets Bevy 0.20.

The ECS library on its own (bevy_ecs)

  • Usable standalone, without the renderer or UI.
  • Size, counted from the published source: 0.20.0 is about 120,000 lines including docs and tests, about 71,000 lines of code, with 23 dependencies. 0.18.1 was about 104,000 and 62,000, so it grew about 15% in a year.
  • The part a game uses day to day is small: components, queries with filters, systems, schedules with ordered sets, Commands, Messages, resources.

How much the ECS changes. Headings in the official migration guides that touch ECS terms:

UpgradeAll entriesECS-related headings
0.16 → 0.1711726, including the big split of events into buffered messages and observers
0.17 → 0.186411
0.18 → 0.1910311
0.19 → 0.20759

The keyword match overcounts (some are rendering or UI items that mention a component), so roughly six or seven real ECS changes per recent release. They’re mostly renames and changes to advanced features (observers, access tracking, entity allocation, error handling). Components, queries, systems, schedules and commands have been fairly stable. A prototype can pin one version and never upgrade.

Conventions confirmed

  • Bevy, Unity’s ECS and Flecs (a C ECS library) all order systems in a schedule (Flecs has named phases), buffer structural changes and apply them at sync points, and treat unordered conflicting access as the main source of bugs.
  • Bevy’s ScheduleBuildSettings has ambiguity detection (ignore by default; can warn or error) and inserts sync points automatically when systems using Commands are ordered.
  • Query iteration order isn’t guaranteed; in practice it follows entity creation history. Order-dependent logic should sort explicitly.
  • Decimal math can differ between native code and WebAssembly, another reason to keep the sim in whole numbers.

Downsides

  • Pre-1.0: every release breaks something. One 2026 guide estimates 1–3 days of migration every 3–4 months.
  • Few shipped commercial games. Tiny Glade is the best-known name associated with Bevy; how much of Bevy it uses wasn’t confirmed from a second source.
  • The editor and UI toolkit are young, which matters for a panel-heavy game.
  • Claude’s knowledge of the Bevy API can lag the current version.
  • Setup friction for Rust in the browser: the WebAssembly compile target, matching versions of the generated JavaScript glue, bundler integration, and feature flags some common crates need in the browser (random number generation is the classic one).

TypeScript ECS libraries

Checked November 1, 2026, against each library’s docs, the npm registry and GitHub. The game writes its own core instead (simulation-architecture.md); its API, checked against bevy_ecs 0.20.1, is in ecs-core.md.

Bevy conceptMiniplexKootabitECS 0.4Becsy
ComponentsObject properties“Traits”Arrays you define, of numbers or objectsClasses with typed field schemas
Resources—World traitsAnything on the world objectSingleton components
Queries with and withoutYesYes, plus OrYesYes
Change detectionOnly entity added or removedAdded, Removed, Changed (open bugs in edge cases)ObserversAdded, removed, changed
Relationships—YesYesEntity references
Schedule and order———Ordered from declared access plus before/after rules
Deferred structural changes— (immediate; loops run in reverse so removal is safe)— (immediate)— (immediate)Immediate; removed data stays readable until the next frame
Event queues————
Access declared and enforced———Yes, errors on undeclared access
Latest release2.0.0, July 20230.6.6, April 2026 (test builds to Oct 31, 2026)0.4.0, December 20250.16.1, October 2026 (previous: March 2025)
GitHub stars1,0647651,531299
  • Miniplex: simple and pleasant, but only storage and queries, and no code changes since October 2023 (later commits only update dependencies).
  • Koota (from the team behind Zustand and React Three Fiber): active, with the data model closest to Bevy’s. Pre-1.0, React-first, structural changes are immediate, no schedule. The strongest alternative if a library is ever wanted.
  • bitECS 0.4: a rewrite. Components can now be arrays of objects, so it no longer forces nested data into numbers. Numeric entity ids, no schedule.
  • Becsy: closest to Bevy’s enforcement, but class and decorator heavy, components use typed schemas (nested data falls back to a plain-object type), a small community, sparse releases. It orders writers before readers within a frame, which differs from this game’s phase model and next-tick events.

Swift and Rust

Rust is much closer to Swift than TypeScript is: enums that carry data with exhaustive matching, Option instead of null, Result with a short operator to pass errors up, traits like protocols, constrained generics, value-type structs and immutability by default. The big new idea is ownership and borrowing. TypeScript’s types disappear at runtime, it has escape hatches, and every number is a decimal underneath.

A strict compiler matters more when Claude writes most of the code: Rust refuses to compile unhandled cases, missing values and unsafe shared changes, so whole categories of mistakes never reach a test.

Performance

Rough ranges, with native Rust as the baseline. Not measurements of this game.

EnvironmentUsed forRelative time (lower is faster)Garbage-collection pausesMemory
Rust, nativeServer, tests, sweeps1×NoneLowest
Rust → WebAssembly in a workerSingle playerabout 1.3–2×None in the simLow
JavaScript in NodeServer, sweepsabout 2–3× written carefully, about 5–10× written idiomaticallyYesAbout 3–10× more
JavaScript in a browser workerSingle playerAbout the same as NodeYesAbout 3–10× more

“Carefully” means flat number arrays and no allocations in hot loops. “Idiomatically” means many small objects and maps, which is how code looks unless someone insists otherwise.

Sources and confidence:

  • WebAssembly vs native: Not So Fast (Jangda et al., USENIX ATC 2019) ran SPEC CPU programs and found WebAssembly 45% (Firefox) to 55% (Chrome) slower on average, 2.08× and 2.5× at worst. Earlier near-native results used only tiny kernels. Browsers have improved since.
  • WebAssembly vs JavaScript: thin. One unverified figure of about 1.3× faster on SPEC; blog micro-benchmarks of numeric loops report 4–10×, and note JavaScript can win on small workloads because of WebAssembly’s startup cost.
  • Native Rust vs JavaScript: general language benchmarks, roughly 2–5× on CPU-heavy work.

What it means at Tier 1. By the Tier 2 unlock (36–42 minutes in Run 8, balance-and-simulations.md) a base has dozens to a couple of hundred buildings and a few dozen units: a few thousand entities with margin. A tick has 50 ms. If a tick took 0.1 ms in native Rust, it would take about 0.13–0.2 ms in WebAssembly, 0.2–0.3 ms in careful JavaScript and 0.5–1 ms in idiomatic JavaScript, at most 2% of the budget. The one heavier job, pathfinding on a 640 × 640 map, happens only when a unit needs a new route.

The differences start to matter late: big bases, hundreds of aliens, several worlds on one server, and large balance sweeps. Rust’s lower memory use also matters for hosting persistent worlds. In multiplayer the phone doesn’t run the sim at all, which saves battery as well as time.

If the sim moves to Rust

The planned shape, if it happens (player-and-platform.md):

  • The sim in Rust with bevy_ecs only, no Bevy renderer or UI.
  • Native on the server (with a small Rust WebSocket server, no Node) and for tests and sweeps.
  • WebAssembly inside the same browser worker for single player, with generated glue passing commands in and state out.
  • The client stays TypeScript, PixiJS and HTML, and never knows which language the sim is written in.
  • With whole-number math, the native and WebAssembly builds give identical results, so a single-player world could move to a server.

Costs: two languages, a WebAssembly build step, slower compiles than save-and-refresh, and Bevy’s version churn.

Sources