Engine Architecture / Systems / Research Notes
Rengine: A Compact TypeScript Rendering and Game Engine Experiment
A scene/entity runtime for exploring transform hierarchies, update components, canvas rendering, React rendering, and live debug visualization.
Abstract
Rengine is a compact rendering and game-engine experiment built in TypeScript. It focuses on core engine boundaries: scenes, entities, folder-style transform hierarchies, components, update loops, renderer abstraction, and debug visualization. Entities hold transform properties, component lists, render functions, and optional shaders. Folder entities compose child transforms, scenes group entities, the loop advances component state, and renderer helpers can target canvas or React output. The browser surface exposes those mechanics with live scene selection, zoom controls, wireframe toggles, and an inspectable component tree.
I
Motivation
Rengine isolates the parts of a game or rendering engine that sit below visible content: how entities are represented, how transforms compose through parents, how rendering can be swapped, and how update logic stays separate from drawing.
The visual vocabulary is deliberately small. Boxes, folders, paths, rotations, and debug markers are enough to stress the runtime boundaries without burying the engine model under art assets.
Rengine is best read as a transform debugger that happens to render animated scenes. The question is not whether it can ship a game. The question is whether the runtime makes a child box's final position explainable after its own position, its anchor, a parent folder, a component update, and the renderer's coordinate conventions all interact.
- Primary goal: make scene graph and transform behavior visible.
- Implementation target: a small TypeScript engine with canvas and React render paths.
- Demo target: live inspection of runtime scenes, transforms, and component labels.
II
Entity Model
An entity is a bundle of transform properties, components, a renderer, and shaders. A folder entity is an entity-like grouping node that owns children and applies parent transforms to them. That gives the engine a compact scene graph without needing a separate hierarchy system.
The folder model is the key idea. It lets a parent pivot, move, scale, or rotate a child group, while individual children still keep their own local transforms and update components.
In the implementation, a folder is deliberately practical: it owns child entities, applies a parent transformation before rendering them, and can optionally recenter its origin around the geometric bounds of its children. It is less general than a full scene-graph math library, but the inspector can explain the hierarchy and grouping stays close to ordinary entity behavior.
- Entity construction flows through Rengine.Entity.MakeEntity.
- Folder construction flows through Rengine.Entity.MakeFolderEntity.
- Folder bounds can optionally place origin and anchor at the geometric center.
- Debug visualization shows anchors, positions, and parent-child relationships.
III
Engine Type Model
The engine can be reconstructed from a small set of TypeScript types. Entity is the core drawable/updateable object. FolderEntity extends Entity with children. Scene groups entities and gives them a name. EngineState stores scenes, the active scene, global configuration, camera data, and renderer selection.
type Vector2 = { x: number; y: number };
type Component = {
name: string;
update: (delta: number, e: Entity, s: Scene, prev: EngineState) => [Entity, EngineState];
};
type Entity = {
id: string;
position: Vector2;
anchor: Vector2;
rotation: number;
scale: Vector2;
components: Component[];
render: (state: EngineState) => (entity: Entity) => unknown;
};
type FolderEntity = Entity & { entities: Entity[] };
type Scene = { name: string; entities: Entity[] };This choice keeps traversal uniform. Rendering and updating can visit an Entity, inspect whether it owns children, and recursively traverse without switching to a distinct transform-node API.
IV
Scenes and Runtime Loop
Scenes are named collections of entities, and the engine state tracks both the list of scenes and the active scene. Scene helpers create scenes, activate them, add entities, and remove entities.
The update model is component-oriented. Components receive delta time, the current entity, the scene, and previous engine state, then return an updated entity and state. Movement and animation logic stay attached to entities instead of leaking into the renderer.
Rengine does not implement a full entity-component-system with indexed component stores and query planning. Components are ordered functions on an entity. The smaller model is easier to read and sufficient for the project's goal: show how motion changes entity transforms before those transforms are projected into canvas or React output.
- Demo components include rotations, velocities, square paths, and star paths.
- The active scene is recreated when the selected sample changes.
- The runtime tree is periodically synchronized back into the React inspector.
V
Loop Scheduling
The loop has two modes: interval-based stepping and animation-frame stepping. In both modes the frame operation is the same: compute delta time, tick the active scene, apply the global update hook, and render the new engine state. Delta time is passed into every component so animation speed is independent of frame count.
timestamp | v delta = timestamp - previousTimestamp | v TickActiveScene(delta, state) | v Lifecycle.update(delta, state) | v render(state) | v store state for next frame
The portfolio route caps large deltas before rendering so a suspended tab or stutter does not produce a single enormous movement step in the visible inspection surface.
VI
Transform Composition
Rengine's central mathematical object is the local-to-world transform. Entity position, anchor, rotation, and scale are stored locally. Folder entities compose those local values into world values for their children, which allows nested motion without copying absolute coordinates into every descendant.
Root folder W0
|
+-- Orbit folder M1 -> W1 = W0 M1
|
+-- Box M2 -> W2 = W1 M2
|
+-- Child folder M3 -> W3 = W1 M3The runtime inspector exposes both local and world-space values because most transform bugs are mismatches between the two. A child that appears wrong on screen may have a correct local transform but an unexpected parent transform, or the inverse.
VII
Renderer Abstraction
Rengine separates render functions from entity state. A box renderer can target canvas by drawing into a 2D context, or target React by returning absolutely positioned elements with computed transform styles.
The canvas path also supports transformation-point rendering. Blue, red, and relationship markers expose the distinction between entity position, anchor, and anchor-to-position offset, which is essential when debugging nested transforms.
- Canvas rendering uses translate, rotate, and fillRect against the current entity transform.
- React rendering converts the same transform data into CSS position, scale, and rotation.
- Texture rendering is implemented for the React path as an image renderer.
- Wireframe/debug overlays expose the renderer's transform basis.
VIII
Renderer Contract
Renderers consume engine state and entities; they do not own simulation state. The same scene graph can be stepped once and projected into different output targets. The canvas renderer uses imperative drawing commands, while the React renderer converts transforms into DOM/CSS output.
save context
translate(entity.position)
rotate(entity.rotation)
scale(entity.scale)
translate(-entity.anchor)
draw entity body
restore contextThe debug overlay is part of the renderer contract. Anchor markers, position markers, and relationship lines expose the transform basis used by the renderer, so visual output can be compared against the tree inspector without consulting source code.
IX
Component Behaviors
Components are small deterministic update functions attached to entities. A component may mutate transform fields such as position or rotation, but it returns the updated entity and engine state explicitly. Multiple components on one entity are applied in sequence.
rotation component:
rotation += angularVelocity * delta
velocity component:
position += velocity * delta
square path component:
select edge by phase
move along edge at configured speed
star path component:
interpolate along star vertices by phaseBecause component labels are exposed in the runtime tree, they also function as instrumentation. The inspector can explain why a node moves, not only where it currently is.
X
Browser Inspection Surface
The browser integration turns the engine sandbox into an inspection surface. It keeps the live canvas, adds scene selection, zoom controls, wireframe toggling, scene-focus copy, and a collapsible runtime tree.
That tree is the important bridge between visual output and engine state. The moving scene can be inspected through the corresponding hierarchy, component labels, local/world transforms, anchors, rotations, scales, and node IDs.
- Demo scenes include stacked orbits, counter drift, layered motion, diagonal sweep arrays, in-place arrays, square paths, and star paths.
- The stage zooms from 0.1x to 2x.
- The tree supports local/world-space toggling per node.
- The browser route makes the runtime inspectable without opening the source.
XI
Inspector Snapshot Semantics
The runtime tree is a snapshot of the current engine graph, not the graph itself. Each node stores identity, kind, component labels, local transform, world transform, color, and child snapshots. React inspection state stays separate from the mutable engine state used by the canvas loop.
type RuntimeTreeNode = {
id: string;
label: string;
kind: "entity" | "folder";
componentLabels: string[];
local: TransformSnapshot;
world: TransformSnapshot;
color?: string;
children: RuntimeTreeNode[];
};Collapsed-node state and local/world display preferences live outside the snapshot. When the scene changes, those UI sets are filtered against the new node IDs so stale inspector state cannot refer to removed nodes.
XII
Sample Scene Suite
The sample scenes are not arbitrary; each stresses a different engine property. Stacked orbits stress parent rotation. Counter drift stresses competing component updates. Layered motion stresses folder nesting. Sweep and in-place arrays stress repeated entity construction. Square and star paths stress time-dependent component motion.
stacked orbits -> nested rotation composition counter drift -> opposing component fields layered motion -> multi-level folder transforms diagonal sweep -> repeated entity construction in-place array -> shared origin with varied transforms square/star paths -> scripted component trajectories
The site integration adds a second verification surface. The canvas shows whether motion is visually correct; the runtime tree shows whether the underlying hierarchy and transforms explain that motion.
XIII
Tradeoffs and Scope
Rengine chooses a small, inspectable engine model over a feature-complete game framework. There is no asset pipeline, physics system, input abstraction, collision model, audio layer, or retained editor format. The reward for that narrowness is that every moving part in the demo can be traced to one of four ideas: entity state, folder composition, component updates, or renderer projection.
The canvas and React renderers are also intentionally asymmetric. Canvas is the better fit for wireframe markers and imperative drawing; React is useful for proving that the same entity state can become DOM. Keeping both paths exposes the renderer boundary, even though a production engine would likely commit harder to one primary target.
- Folder entities make hierarchy simple, but they are not a full transform-node system.
- Ordered component functions are easy to inspect, but they do not scale like a data-oriented ECS.
- Debug overlays add visual noise, but they make anchor and position mistakes obvious.
- The demo scenes act as regression examples for transform behavior rather than as finished game content.
XIV
Results and Design Properties
Rengine establishes a compact engine model with explicit scenes, entities, folder entities, component update functions, renderer abstraction, and live inspection. The result is a small but complete environment for validating hierarchical transforms and render-target boundaries in TypeScript.
The strongest result is observability. The same running scene can be inspected as pixels, as a runtime tree, as local transforms, as world transforms, and as component labels. The engine demonstrates transform and update semantics without relying on a large game layer.
- Folder entities provide hierarchy without a separate transform-node type.
- Components update entities through an explicit delta-time contract.
- Canvas and React render paths share entity state while differing in projection target.
- The runtime inspector makes transform composition auditable from the browser surface.