FERRETGame engine over Babylon.js
Ferret

The world

World owns the Babylon scene, the simulation clock and everything that lives in it. A game has one.

The fixed step#

Simulation runs at a fixed rate (60 Hz by default, fixedStep), decoupled from rendering. Each rendered frame calls world.advance(realDt): it runs as many fixed steps as fit in the elapsed time, then the per-frame updates. A stall is never simulated as one long jump: frame time is capped at maxFrame (0.1 s by default).

Because the step is fixed, a run with the same inputs repeats exactly. That is what makes replays, networking and tests possible: tests drive a world with world.step() or world.run(seconds) and no renderer.

MemberUse
timeScaleglobal slow motion (below 1) or fast forward
pausedstops the simulation; rendering goes on
time, tickCountsimulated seconds and number of fixed steps so far
alphahow far the rendered frame is between the last two steps, for interpolating what the camera sees
entity.timeDilationper-entity dilation: a game may slow its NPCs without slowing the player

Entities and components#

An Entity wraps a Babylon TransformNode and carries components. A Component is a plain class with a few optional hooks:

class Bob extends Component<{ amplitude: number }> {
  onAttach()           { /* attached to an entity */ }
  onStart()            { /* before the first update */ }
  onFixedUpdate(dt)    { /* simulation: at the fixed rate */ }
  onUpdate(dt)         { /* presentation: once per rendered frame */ }
  onDetach()           { /* removed */ }
}

const e = world.spawn('crate', { position: new BABYLON.Vector3(0, 1, 0), tags: ['prop'] });
e.add(Bob, { amplitude: 0.1 });

Put game logic in onFixedUpdate and things that only need to look right in onUpdate. Components are found with entity.get(Cls) or require(Cls); the world finds entities by id, name, tag or component (entity, findByName, findByTag, entitiesWith).

params are plain data. That is deliberate: a level manifest names a component and gives its params, and Registry maps the name to the class (world.components, world.behaviours). A game registers its classes once; levels then wire them without code.

Services#

A Service is a world-wide singleton: input, levels, zones, physics, UI. It has an optional init(world), fixedUpdate(dt) (before the components' fixed update), update(dt) and dispose(). Add one with world.addService(s), find it with world.service('name'). createGame adds input and physics; you add what the game uses.

Events and commands#

EventBus is a small synchronous bus (on, once, off, emit) on world.events. The engine reports what happens through it: trigger:enter, trigger:exit, character:jump, character:land, physics:contact, world:step and many more, each documented where it is emitted.

CommandChannel (world.commands) is the other side: named commands with a description that a tool, the inspector or an AI agent can list and call. Every package registers its own; see Commands.

Logging#

Logger writes by scope and level to sinks; on OS Beroun a sink posts to the shell's log, in a browser to the console.