Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
This file is generated from Conventional Commits as part of the release process. See the "Changelog" section of AGENTS.md for how entries are produced and what should (and should not) be edited by hand.
[Unreleased]
[0.25.5] - 2026-09-19
Changed
- physics:
TerrainCollidernow models its heightmap as a continuous chain of surface edges (surface, replacingsegments) rather than a row of closed quadrilaterals, following the same design as a Box2D chain shape. Narrow-phase collision runs against that chain only, so a contact normal always comes from the ground's actual surface and never from an interior boundary between two neighboring stretches of it. The solid slab is still whatcomputeAabbandraycastTerrainsee, so broad-phase culling and raycasting are unchanged. ATerrainCollideris now one-sided: a body underneath the terrain is pushed back up through its surface instead of out of its bottom, and one that has passed entirely out of the bottom of the slab stops colliding - physics:
detectCollisionnow returns an array of manifolds instead of a single manifold ornull, as dodetectCircleTerrainCollisionanddetectPolygonTerrainCollision. A body wide enough to straddle several of aTerrainCollider's surface edges gets one manifold per edge it touches, each carrying its own feature ids, so every contact warm-starts independently across ticks instead of a single contact changing hands between edges.createNarrowPhaseEcsSystemwrites them all intocollisionManifolds, so a single entity pair can now contribute more than one manifold per tick - physics: Add
NarrowPhaseManifold(aCollisionManifoldwithout its entity ids) andclipAgainstReferenceFace(the reference/incident face clipping stagedetectPolygonFacesCollisionalready used internally) to the public API
Fixed
- physics: A body resting on or rolling across a
TerrainCollidercan no longer be pushed sideways along the ground it is resting on. Closing each stretch of ground off into its own quadrilateral gave the boundary between two neighboring stretches a face of its own to collide with, and for a body wide enough to reach past the ground beneath it that face could beat every real surface face on penetration depth - producing a fully horizontal contact normal on gently rolling ground
[0.25.4] - 2026-09-18
Changed
- rendering:
createTerrainRenderEcsSystemno longer takes aTerrainMeshparameter; it now draws every entity with a newTerrainMeshEcsComponent(attached viaaddTerrainMeshComponent), so a world can have any number of independently culling-masked terrain meshes instead of exactly one baked into the system at creation. It also managesRenderContext.clearStrategyitself whenever a terrain mesh is present, so callers no longer need to setclearStrategy = CLEAR_STRATEGY.noneby hand to avoid the sprite pipeline wiping the terrain mesh each frame
Fixed
- physics:
TerrainCollidernarrow-phase (detectCircleTerrainCollision/detectPolygonTerrainCollision) no longer flips which terrain segment "wins" on sub-pixel floating-point noise when a body rests across several near-coplanar segments at once. The unstable selection used to resetcreateCollisionResolutionEcsSystem's warm-started accumulated impulse every time it flipped, preventing the contact from ever fully converging and causing visible steady-state sinking under moderate-to-heavy loads
[0.25.3] - 2026-09-17
Added
- common: Add the
Resizableinterface (width,height,resize(width, height)), implemented byRenderContext - utilities: Add
createContainerResizeSync(container, resizables), watching a container element and callingresize()on everyResizable(e.g. aRenderContext) whenever its size changes - the container-resize handling that used to live insideGame, now a standalone utility with its own independent lifecycle - rendering: Add
importTexture, a simple texture import pipeline that converts a texture's pixel dimensions into a world-unit size via apixelsPerUnitsetting (mirroring a per-texture "Pixels Per Unit" import setting)
Changed
- utilities:
Game's constructor now takes an array ofEcsWorlds instead of a single one, so oneGamecan drive more than one world, e.g. a gameplay world alongside a separate UI overlay world.Gameis now a simple loop orchestrator with no notion of rendering or resizing at all: it no longer accepts aRenderContext/Resizable, or does anyResizeObserverwiring - that responsibility moved to the newcreateContainerResizeSync.createGameis unaffected externally and still returns a singleworld/renderContextpair (now passed toGameas a one-element array), plus a newresizeSynckeeping the render context's canvas sized to its container - rendering:
createImageSpritenow sizes a sprite viaimportTexture, using its ownpixelsPerUnitoption (default100) instead of using the texture's pixel dimensions directly as world units. This is a behavior change: passpixelsPerUnit: 1to keep a sprite's previous size
[0.25.2] - 2026-09-15
No user-facing changes.
[0.25.1] - 2026-09-14
Added
- docs: The documentation site's homepage hero now plays a looping, blurred background video
Fixed
- utilities:
Gamenow resizes itsRenderContextto match its container whenever the container's size changes (via aResizeObserver, started onrun()and stopped onstop()), so a running game's canvas, camera projection, and UI layout follow a resized window/container instead of staying pinned to whatever size the container was when the game started.createGamewires this up automatically. The actual resize is deferred to the next animation frame rather than done synchronously in theResizeObservercallback, since mutating the canvas's size directly in response to a resize notification is what triggers the browser's "ResizeObserver loop completed with undelivered notifications" error - demos: Fix the space shooter demo leaving bare canvas past the background's edges (and its bloom/blur post-processing staying at a stale resolution) after a resize, now that resizing actually reaches the demo - the background's world size and its shader's
u_resolutionuniform are recalculated whenever the render context resizes, and the demo's background/foregroundRenderTargets resize along with it. The documentation site's demo fullscreen toggle also no longer tears down and recreates the whole game to pick up the container's new size, sinceGamenow resizes in place - demos: Fix the UI Main Menu demo's left nav panel keeping a fixed on-screen pixel width instead of scaling with the rest of the screen, now that resizing actually reaches the demo - it now uses a plain reference-pixel width like every other element in the demo, so it keeps the same proportion of the screen at any window size
[0.25.0] - 2026-09-13
Added
- text: Add
@forge-game-engine/forge/text's font atlas pipeline (Phase 1 of MSDF text rendering, no rendering yet):npm run generate-font-atlasturns a.ttf/.otffont into a multi-channel signed distance field (MSDF) atlas PNG plus a versionedFontAtlasDataJSON, andFontAtlasCacheloads that pair into aFontAtlasat runtime - text: Add static single-line text rendering (Phase 2 of MSDF text rendering):
addTextComponent/TextEcsComponent(string,FontAtlas, size, color, letter spacing, layer) is shaped bycreateTextShapingEcsSysteminto a cachedTextMeshEcsComponent(kerned glyph quads + bounds), drawn throughcreateRenderEcsSystemvia the same instanced sprite batching nine-slice sprites use, with a new MSDF fragment shader that stays crisp at any scale - text: Add multi-line layout (Phase 3 of MSDF text rendering):
TextEcsComponent.maxWidthgreedily word-wraps text across multiple lines,horizontalAlign('left'|'center'|'right'|'justify') andverticalAlign('top'|'middle'|'bottom') position lines withinmaxWidthand the block against the entity's position, andlineHeightcontrols the spacing between line baselines.center/rightalign each line againstmaxWidthitself, not just against each other - a single unwrapped line (or any line as wide as the block's own widest line) previously always computed a zero offset, silently no-op'ingcenter/rightfor the single-line case regardless of value - text: Add outline and soft-shadow/glow effects (Phase 4 of MSDF text rendering):
TextEcsComponent.outlineColor/outlineWidthandshadowColor/shadowOffset/shadowSoftness, in screen-pixel-range units so an effect's size stays constant on screen regardless of camera zoom or entity scale. Every glyph's outline/shadow draws in its own pass, always completing before any glyph's fill draws on top of it, so both effects render at the same, uniform strength across a whole word (including tightly-kerned pairs like "rg") and can safely merge with a neighboring glyph's own effect - bridging the gap between letters, like a Photoshop "stroke"/"outer glow" layer effect - without ever painting over any glyph's fill; the only limit on either effect is the font atlas's own encodeddistanceRangebudget, identical foroutlineWidthandshadowSoftness/shadowOffsetalike - see the new Text Effects doc for the safe range and how it scales with the text's on-screen size - text: Ship a pre-generated default MSDF font atlas (Liberation Sans, SIL OFL 1.1) at
assets/fonts/default/default.jsonin the@forge-game-engine/forgepackage (Phase 5 of MSDF text rendering), soaddTextComponent/FontAtlasCacherender text with zero font setup - no font file, license, orforge-generate-font-atlasrun required to get started. The documentation site's text demo now loads this shipped atlas directly and adds an interactive playground (live text, size, alignment, wrap, and outline/glow controls - width, offset, and softness) driven bycreateTextShapingEcsSystem's existing dirty tracking - physics: Add
raycast(world, start, end, sort?), casting a line segment against every entity in anEcsWorldwith aColliderEcsComponent(CircleCollider,PolygonCollider, andTerrainCollideralike) and returning every intersection as aRaycastHit(entity,point,normal,distance), ordered by distance fromstartby default - physics: Add
RigidBodyEcsComponent.type('dynamic'|'kinematic'|'static', defaulting to'dynamic'), letting a body be moved directly by game code ('kinematic') so it still pushes dynamic bodies on contact without itself being affected by gravity, forces, or impulses - previously only possible implicitly, by giving an entity noRigidBodyEcsComponentat all (still supported, and equivalent totype: 'static') - utilities: Add
DirectedAcyclicGraph<T>, a generic topological-ordering data structure (addNode,addEdge,removeNode,topologicalSort) that throws a descriptive error when an edge would create a cycle or reference an unregistered node - ecs: Add
EcsWorld.addSystem'sbefore/afteroptions, letting a system be ordered relative to specific other systems instead of an arbitrary numeric priority, andEcsWorld.addSystemGroup/createSystemGroup, for ordering whole groups of systems (each with their ownbefore/after) against each other - see the "Ordering systems with before/after" and "Grouping systems" sections of the ECS World doc - ecs: Add
EcsWorld.getComponentAccessor(componentKey), resolving a component's storage once and returning a fastentity => component | nullaccessor, for a system that looks up the same optional component (one it can't add to its ownquerybecause not every matched entity has it) for many entities in a tight loop instead of callinggetComponent- which re-resolvescomponentKeyon every call - repeatedly - input: Add canvas-space pointer state directly on
MouseInputSource:position,delta,scroll,buttonsDown,buttonsHeld, andbuttonsUp, for code (a UI hit-tester, a drag gesture, a debug overlay) that wants the raw device state without an interveningInputActionbinding - rendering: Add
SpriteEcsComponent.sortDepth, an optional per-sprite override for the depth a sprite is sorted by within itslayer, taking priority over the existing default (position.world.y) when set - ui: Add
@forge-game-engine/forge/ui's layout core:RectTransformEcsComponent/addRectTransformComponentand the pureresolveRectanchor/pivot/stretch resolver,UiAnchorpresets (point, edge-stretch, and full-stretch anchors),CanvasEcsComponent/createUiCanvas(a dedicated, static UI camera with its own transparent-clearedRenderTarget, isolated from the world by a caller-suppliedcullingMask- Forge doesn't reserve or default to any particular render category bit for UI, so pick one your game isn't already using elsewhere and reuse it for every UI visual's own category),createUiLayoutEcsSystem(a top-down, hierarchy-pre-order layout resolve that writesposition.local, spritewidth/height/pivot, andsortDepthon both sprites and text - so a panel's draw order relative to its own label never depends on where it happens to sit in the world), andcreatePanel/createLabelaggregate factories. See the new UI doc and demo for a HUD example - ui: Add interaction:
UiInteractableEcsComponent/addUiInteractableComponent(onInvoke,onPointerEnter/onPointerExit/onPointerDown/onPointerUp,onBeginDrag/onDrag/onEndDrag, andisHovered/isFocused/isPressed/isDragging/wasInvokedThisFramestate),createUiRaycastEcsSystem(a reverse-hierarchy-order, first-hit-wins pointer hit test, publishingCanvasEcsComponent.hoveredEntity/isPointerOverUi),createUiInteractionEcsSystem(the pointer hover/press/drag state machine, correct even when a pointer enter-and-press or press-and-release land in the same tick),UiFocusEcsComponent/createUiNavigationEcsSystem(automatic nearest-neighbor gamepad/keyboard focus navigation, with per-side explicit overrides, plussubmitInput/cancelInput),UiColorTransitionEcsComponent/createUiTransitionEcsSystem(an eased tint per interaction state), theUiNavigationDirection/uiNavigationDirectionsmap, and thecreateButtonaggregate factory. Pointer hit-testing and interaction are driven through the structuralUiPointerSourceinterface rather than a hard dependency onMouseInputSource, so a touchscreen, stylus, or other pointing device can drive the same systems;createUiCanvas'spointerSourceoption accepts anything satisfying that shape (MouseInputSourcedoes, unchanged). Invocation (onInvoke) is source-agnostic - the same event fires whether a pointer click or a gamepad/keyboard submit action caused it. See the new UI doc's "Interaction" section and the updated demo for a clickable, focus-navigable button. Breaking change:createUiCanvasnow takes a requiredtime: Timeargument (createUiCanvas(world, renderContext, time, options?)), driving the new color-transition tweens - text: Add
TextEcsComponent.category, an optional per-entity override for the render category glyphs draw with (matched against a camera'scullingMask, the same conventionSpriteEcsComponent/Renderablealready use), defaulting to the existingTEXT_RENDER_CATEGORY- an ordinary default, not a value the engine reserves or forces onto every text entity - text: Add
TextEcsComponent.sortDepth, mirroringSpriteEcsComponent.sortDepth- an optional per-text override for the depth it's sorted by within itslayer, taking priority over the existing default (position.world.y) when set - text: Add
TextEcsComponent.verticalAlign: 'baseline' | 'capline', alongside the existing'top'/'middle'/'bottom':'baseline'anchors the first line's own baseline directly (most useful for single-line text), and'capline'is'top'anchored to the font's newcapHeightmetric (a capital letter's actual top) instead of itsascender(the font's tallest glyphs, including ascenders like "b"/"d"/"h" that reach higher than a flat capital) - useful for a title or label set in caps, where anchoring to the taller ascender leaves a visible gap above the text.FontAtlasData.metrics.capHeightis measured from whichever flat-top capital (H/I/E/F/L/T) is present in the atlas's charset;forge-generate-font-atlascomputes it automatically, and the shipped default atlas has been regenerated with it. Breaking change: the font atlas file format bumps toformatVersion: 2for the new requiredcapHeightfield - any atlas generated with an olderforge-generate-font-atlasmust be regenerated. - text: Add exported
textHorizontalAlignments/TextHorizontalAlignandtextVerticalAlignments/TextVerticalAlignmaps forTextEcsComponent.horizontalAlign/verticalAlign, matching the existinguiScaleModes/mouseButtons/keyCodesconst-map convention, so callers referencetextHorizontalAlignments.centerinstead of the raw string literal'center' - ui: Add controls:
UiToggleEcsComponent/UiToggleGroupEcsComponent/createUiToggleEcsSystem/createToggle(checkboxes, and mutually-exclusive radio groups via a sharedUiToggleGroupEcsComponent),UiSliderEcsComponent/createUiSliderEcsSystem/createSlider(a click-and-drag track with a handle and optional fill,minValue/maxValue/wholeNumbers),UiProgressBarEcsComponent/createUiProgressBarEcsSystem/createProgressBar(a read-only linear fill indicator driven byvalue, with no interaction dependency so avaluewrite is reflected the same frame), andUiDropdownEcsComponent/createDropdown(a header showing the selected option plus a click-to-open list of option rows, each an ordinarycreateButton). See the new UI doc's "Controls" section and the updated demo - ui: Add
UiAnchor.stretchHorizontalLeft/stretchTopLeft, left-pivoted variants ofstretchHorizontal/stretchTopfor aTextEcsComponentlabel that needs to stay centered viahorizontalAlign/maxWidth(a center-pivoted anchor's local x = 0 sits at the box's middle, not its left edge, which is what that alignment box is actually measured from).createUiLayoutEcsSystemnow also keeps a stretch-x-anchored text entity'smaxWidthin sync with its resolved rect's width every frame, so a label anchored this way centers correctly with no caller-side measurement, even against a dynamically-sized parent (e.g. a title in a full-width top bar) or text that changes later. See the UI doc's updated "Labels" section - ui: Add layout groups:
LayoutElementEcsComponent/addLayoutElementComponent(min/preferred/flexible size overrides, plusignoreLayout),HorizontalLayoutGroupEcsComponent/VerticalLayoutGroupEcsComponent(addHorizontalLayoutGroupComponent/addVerticalLayoutGroupComponent) andGridLayoutGroupEcsComponent/addGridLayoutGroupComponent, all arranged by the newcreateUiLayoutGroupEcsSystem- a two-pass, bottom-up-measure/top-down-arrange system that resizes and positions a group's direct children (nesting freely, including a group measuring another group's own content), plusContentSizeFitterEcsComponent/addContentSizeFitterComponent(shrink-wraps an entity to its own measured content) andAspectRatioFitterEcsComponent/addAspectRatioFitterComponent/createUiAspectRatioFitterEcsSystem(widthControlsHeight/heightControlsWidth/fitInParent/envelopeParent).createUiCanvasregisters both new systems automatically, beforecreateUiLayoutEcsSystem. See the new UI doc's "Layout groups" section - ui: Add a chevron indicator to
createDropdown's header, flipping betweenv(closed) and^(open) in step withdropdown.isOpen- the bundled default font atlas is ASCII-only, so these stand in for a down/up-pointing triangle. The header's own label now reserves space on the right edge for it, exposed on the returnedDropdownas a newchevronlabel entity - ui: Add column-aligned form layout:
LayoutElementEcsComponent.sizeToText(also settable viacreateLabel's newsizeToTextoption) sizes a label to its own shaped text instead of a hand-set rect size, andGridLayoutGroupEcsComponent's newcolumnWidthMode/rowHeightMode: 'fixed' | 'content'fields derive a column's width (or row's height) from the largest measured cell placed in it, withcellAlignmentcontrolling where a smaller cell sits within its shared column/row. Together these let a label/control grid (a settings form, an inspector panel) align every row's control to the widest label automatically, with no hand-computed pixel offsets -columnWidthMode/rowHeightMode: 'content'requiresconstraint: 'fixedColumnCount' | 'fixedRowCount'and throws for'flexible'. See the UI doc's updated "Labels" and "Layout groups" sections - ui: Add
UiAnchor.stretchTopRight, the right-pivoted mirror ofstretchTopLeft, for a full-width top-pinned band whose local origin should sit on the right edge (manual position math, a non-text child) rather than the left or center - ui: Add world-space canvas render mode (Phase 5 polish of the UI system design):
CanvasEcsComponent.renderMode/createUiCanvas's newrenderModeoption ('screenSpace'(default) |'worldSpace') puts a canvas's content in the game world instead of overlaid on the screen - diegetic UI like a health bar over an enemy's head or a name tag. A world-space canvas's root rect is an ordinaryRectTransformEcsComponent(sized via the newanchor/anchoredPositionoptions, mirroringcreatePanel) positioned through the normal entity hierarchy rather than the render destination's size, and draws through whichever camera the newcameraoption names - typically the game's own world camera, viacreateUiLayoutEcsSystem, rather than a dedicated UI cameracreateUiCanvascreates for'screenSpace'mode. Also addsUiWorldSpaceFollowEcsComponent/addUiWorldSpaceFollowComponentandcreateUiWorldSpaceFollowEcsSystem(registered by the caller, once, aftercreateTransformEcsSystem- unlike the rest of the UI pipeline): a world-space canvas that followstarget's world position only, never its rotation, so a health bar stays upright above its target instead of swinging around as the target turns - deliberately a separate mechanism fromaddParentComponent, whose meaning (inherit the full world transform) stays exactly as it already was for every other entity in the engine. See the UI doc's new "World-space canvases" section. Breaking change:createUiCanvas's options (CreateUiCanvasOptions) andCanvasEcsComponentare now discriminated unions onrenderMode, replacingCreateUiCanvasRequiredOptions/CreateUiCanvasDefaultedOptions-ScreenSpaceUiCanvasOptionsrequirescullingMaskand allowsreferenceResolution/scaleMode/layer,WorldSpaceUiCanvasOptionsrequirescameraand allowsanchor/anchoredPosition, and passing the wrong combination for a givenrenderModeis now a compile-time type error instead of a thrown Error - ui: Add
CanvasGroupEcsComponent/addCanvasGroupComponent(Phase 5 polish of the UI system design):alpha/interactable/blocksRaycastsapply to a whole subtree at once - fading, disabling, or making click-through an entire panel/modal with one component instead of one per element.alphamultiplies into every descendant's rendered opacity via the newcreateUiCanvasGroupEcsSystem(registered automatically bycreateUiCanvas), on top of - not instead of - each element's own tint/color alpha;interactable/blocksRaycastscombine the same way but are read on demand by the raycast/interaction/navigation systems. Nested groups multiply/AND together up the parent chain;ignoreParentGroupsopts a subtree out of inheriting from its ancestors. See the UI doc's new "Canvas groups" section - rendering: Add
SpriteEcsComponent.opacityMultiplier, an optional additional multiplier applied totintColor.awhen computing a sprite's final rendered alpha - kept separate fromtintColor.aitself so a system (e.g. the newCanvasGroupEcsComponent) can apply an environment-wide fade without overwriting, and later needing to restore, whatever alpha a caller authored ontintColor - text: Add
TextEcsComponent.opacityMultiplier, mirroringSpriteEcsComponent.opacityMultiplierfor a text entity's fill - its outline/shadow effects don't currently inherit it - ui: Add tooltips (Phase 5 polish of the UI system design):
createTooltipattaches a floating panel/label to an already-interactable entity, parented directly to it and pinned just above its top edge so it follows automatically as an ordinary UI child;TooltipEcsComponent/createUiTooltipEcsSystem(registered automatically bycreateUiCanvas) show it once the source readshover/pressedunderderiveUiInteractionVisualState- hovered by pointer or focused by gamepad/keyboard - continuously for at leastshowDelayMilliseconds(defaults to400), hiding it immediately once that ends. See the UI doc's new "Tooltips" section - ui: Add a UI-safe-area concept for notched displays (Phase 5 polish of the UI system design):
UiSafeAreaEcsComponent/addUiSafeAreaComponenton aUiAnchor.stretchAll()-anchored element, combined withcreateUiCanvas's newgetSafeAreaInsetsoption, registerscreateUiSafeAreaEcsSystemto shrink that element's rect inward from a device's notch/camera cutout/rounded corners/home indicator every frame, converted from CSS pixels into that canvas's own UI world units. Per-edge (top/right/bottom/left) opt-out for content that intentionally extends under one edge. See the UI doc's new "Safe area" section - rendering: Add
getSafeAreaInsets, reading a browser viewport's currentenv(safe-area-inset-*)values back into plain numbers (there's no direct JS API for these) via the standard hidden-probe-element technique - returns all zeroes in a browser with no notch/cutout or withoutenv()support, so it's always safe to call unconditionally - ui: Add a
fitReferenceResolutionUiScaleMode, alongside the existingscaleWithScreenSize/matchWidth/constantPixelSize: it keeps a screen-space canvas's root rect at leastreferenceResolutionon both axes, letterboxing/pillarboxing whichever axis has excess space instead of cropping or squashing it - useful for a UI with content anchored out to all four edges, which the other modes can each only guarantee for one axis at a time - ui: Add
sizeUnit/marginUnit: 'screenPixels'toUiAxis.point/UiAxis.stretch(andwidthUnit/heightUnittoUiAnchor's edge-pinned band presets), letting one element'ssize/marginstay a literal, unscaled on-screen pixel count instead of scaling with its canvas's live scale factor like the rest of the anchor system does by default - useful for a fixed-width sidebar or a HUD icon that should hold a constant on-screen size regardless of the window's aspect ratio or resolution. See the UI doc's new "Pixel-locking one element withscreenPixels" section
Changed
- math:
Vector2/Vector3are now plain{ x, y }/{ x, y, z }objects instead of classes, constructed with an object literal ({ x: 1, y: 2 }) and operated on viaVec2/Vec3static methods (Vec2.add,Vec2.rotate,Vec2.normalize, etc.) that mutate their first (target) argument in place and return it, rather than allocating a new vector, for performance in hot loops like physics integration; see the "Vectors and Rectangles" doc for the full API and migration guidance. Breaking change. - math:
Vec2.normalize/Vec3.normalizenow throw when given a zero-length vector instead of silently returning it unchanged, since a normalized direction is undefined for a zero vector. Breaking change. - utils:
createGameutility now throws a more useful error message when no matching DOM element is found that has the id matchingcontainerId - utils:
createImageSpritenow makes layer an optional value in the options argument rather than a required argument to the function. Defaults to1. - math:
Rectis now a plain{ min, max }object instead of an{ origin, size }class, constructed with an object literal and operated on via the newRectsstatic-method namespace (Rects.contains,Rects.intersects,Rects.size,Rects.clone) rather than instance methods, matching theVector2/Vec2convention.Rectwas constructed nowhere in the engine outside its own tests, so this is not expected to affect existing content. - ui:
RectTransformEcsComponent'sanchorMin/anchorMax/pivot/sizeOrMarginfields are replaced by per-axisx/yfields of the newUiAxistype: aUiPointAxis({ kind: 'point', anchor, pivot, size }) or aUiStretchAxis({ kind: 'stretch', anchorMin, anchorMax, pivot, margin }), built withUiAxis.point/UiAxis.stretch. Previously,sizeOrMargin'sx/ycomponents silently meant different things (a literal size or a stretch margin) depending on that axis's anchoring, with nothing preventing the wrong one from being set; the type checker now rules that out; a point axis has nomarginfield to set, and a stretch axis has nosizefield.UiAnchor's presets (topLeft,stretchTop,stretchAll, etc.) are now factory functions taking the size/margin values their own axes actually need (e.g.UiAnchor.center({ x: 200, y: 60 }),UiAnchor.stretchTop({ height: 64, horizontalMargin: 0 })) instead of plain objects spread alongside a separatesizeOrMarginoption;createPanel/createLabel/createButton/createToggle/createSlider/createProgressBar/createDropdown'ssizeOrMarginoption is removed - pass the size/margin into theanchoroption's factory call instead. See the UI doc's updated "RectTransform" section. Breaking change. - ui: Add
registerUiSystems(world, renderContext, time, options?), and simplifycreateUiCanvas(world, renderContext, options)to only create a canvas entity - it no longer registers any systems, and no longer takes atimeargument. Previously,createUiCanvasregistered every UI system as a side effect, guarded by module-level state so a second call (for a second canvas on the same world) wouldn't register the layout/interaction pipeline twice; that state had no way to detect a system being removed from the world later, and gave no error if a caller registered a UI system directly and then calledcreateUiCanvastoo.registerUiSystemsnow does this instead - called once perEcsWorld, the same wayregisterInputsis - socreateUiCanvasis a plain, freely-repeatable factory like every othercreate*function in the module, and there's no hidden bookkeeping that can drift out of sync with the world's actual state.pointerSource/getSafeAreaInsetsmove fromcreateUiCanvas's options toregisterUiSystems's, since they configure the shared pipeline for the whole world, not one canvas. See the updated "Creating a Canvas" doc. Breaking change. - ui:
GridLayoutGroupEcsComponent/addGridLayoutGroupComponent'sconstraint/columnWidthMode/rowHeightModefields are now a discriminated union onconstraint:columnWidthMode/rowHeightMode: 'content'is only assignable onceconstraintis'fixedColumnCount'/'fixedRowCount', a compile-time error instead of the thrownErrorit was previously for'flexible'. Breaking change. - rendering:
createRenderEcsSystemnow orders sprites/glyphs for drawing with a linear-time counting sort instead of a general-purpose comparison sort, measurably reducing per-frame cost in scenes with many sprites. Draw order (bylayerthen depth) is unaffected, including tie-breaking - sprites at the same depth still draw in the same relative order they did before. - rendering:
createRenderEcsSystemnow resolves each sprite's optional rotation/scale/flip components via the newEcsWorld.getComponentAccessoronce per frame instead of callingEcsWorld.getComponentthree times per sprite, measurably reducing per-frame cost in scenes with many sprites. No behavior change.
Removed
- math: Removed the
Vector2/Vector3classes, includingnew Vector2(...)/new Vector3(...)construction, their instance methods (.add(),.subtract(),.clone(), etc.), and their static constant getters (Vector2.zero,Vector2.up, etc.), replaced by theVec2/Vec3static-method API above. Breaking change. - ecs: Removed
EcsWorld.addSystem's numericregistrationOrderparameter and theSystemRegistrationOrderconstants, replaced by thebefore/afteroptions and system groups described above - an arbitrary priority number didn't tell you how many systems would run before yours, or what priorities were already taken, and gave no way to express "run before/after this specific system". Breaking change. - utilities: Removed
SortedSet, which existed solely to backEcsWorld's old priority-ordered system list and has no other consumers; useDirectedAcyclicGraphfor ordering use cases going forward. Breaking change. - common: Removed
DepthEcsComponent/addDepthComponent, which was never read by anything in the engine; use the newSpriteEcsComponent.sortDepthfor controlling sprite draw order instead. Breaking change.
Fixed
- common: Fix
createTransformEcsSystemcomposing a child's world position by plain addition of the parent's world position, ignoring the parent's world rotation and scale entirely - a child parented to a rotated and/or scaled entity now orbits/scales with it instead of spinning or resizing in place at an un-rotated, un-scaled offset. Behavior change for any existing content that parents a positioned entity to a rotated or scaled one and was authored against the previous (incorrect) composition. - rendering: Fix
SpriteEcsComponent.pivotusing a Y-down convention ((0, 0)was the sprite's top-left) while every other Y-facing value in the engine (world position, rotation) is Y-up -pivotis now Y-up too, so(0, 0)is the bottom-left corner and(1, 1)is the top-right corner. Nine-slice region placement (computeNineSliceRegions) is updated to match. The centered default ((0.5, 0.5)) is unaffected. Breaking change for any content using a non-centered pivot: those sprites now render vertically mirrored around their old pivot point until the pivot'syis updated (1 - oldPivotY). - utilities: Fix
Game's render loop occasionally producing a negativedeltaTimefor one frame (e.g. right after constructing hundreds of entities in a single tick), by readingperformance.now()at the point the frame callback runs instead of trustingrequestAnimationFrame's supplied timestamp, which is not guaranteed to be monotonic relative to the previous frame - input: Fix
MouseInputSourcecaching its container'sgetBoundingClientRect()once in its constructor and reusing it for everymousemoveevent - cursor positions (and any binding derived from them) were wrong after the container was resized, scrolled, or otherwise reflowed. The bounding rect is now recomputed fresh on everymousemove - text: Fix
forge-generate-font-atlasderivingFontAtlasMetrics.ascender/descenderfrom the font's nominalbase/lineHeightBMFont fields instead of the atlas's actually rendered glyph ink - for the shipped default atlas (Liberation Sans) this undershot both bounds (ascenderwas smaller than the rendered top of "b"/"d"/"h"/"i"/"l",descenderwas smaller in magnitude than the rendered bottom of "("/")"/"j"), so everyverticalAlign('top','middle','bottom') positioned text noticeably higher than intended, with'top'-aligned ascenders visibly poking above their anchor.ascender/descenderare now the actual max/min glyph bounds across the atlas's charset; regenerate any existing atlas withforge-generate-font-atlasto pick up the fix - text: Fix
TextEcsComponent.verticalAlign: 'middle'centering on the font's ownascender/descendermetrics instead of the shaped text's actual rendered ink - since a font's ascender is typically taller than its descender is deep (most glyphs have no descender at all), this systematically positioned every descender-less string (numbers, titles, most short UI labels, e.g. a"Score: 1234"HUD readout) above the true visual center of its box.'middle'now centers on this specific string's own rendered bounds instead;'top'/'bottom'are unchanged (still anchored to the font's metrics, so a line's position stays stable as its text is edited). Behavior change for existing content relying on the old, metrics-based'middle'centering. - ui: Fix
createButton's label reading as shifted right of the button's true center -horizontalAlign: 'center'only re-centers text within an explicitmaxWidthbox, whichcreateButtonnever set, so a label always started exactly at the button's center point and grew rightward from there instead of being centered on it. The label now setsmaxWidth/horizontalAlign(defaultingmaxWidthtosizeDelta.x, overridable via the newlabelMaxWidthoption for a stretch-anchored button, wheresizeDelta.xis a margin rather than a width) so the engine's own centering keeps the label centered even as its text changes later, instead of a one-off pre-measured offset that only matched the label's initial text. This also fixescreateDropdown's header and option-row labels, which are built fromcreateButton- including the header staying centered after selecting a different option, and the option rows (which stretch to the header's width) centering against that width instead of their own zero-widthsizeDelta. - ui: Fix
createUiInteractionEcsSystemre-focusing a still-hovered interactable on every tick instead of only on the hover-enter edge - a pointer left resting over an element (not actively moving) silently re-asserted focus onto it every tick, always winning over (and effectively blocking) any keyboard/gamepad focus navigation made in the meantime, since interaction runs after navigation in the pipeline. Most visible with a dropdown: opening it via keyboard while the mouse happened to be resting on the header made arrow keys unable to move focus into the now-visible option rows at all. Hovering still focuses an element the moment the pointer moves onto it - it just no longer keeps re-claiming focus back from keyboard/gamepad input every frame afterward. - ui/text: Fix
horizontalAlign: 'center'/'right'/'justify'measuring its alignment box from aTextEcsComponent's own localx = 0, which only lands on the visible box's actual left edge for a left (pivot.x: 0) anchor - a stretch-x anchor with a center pivot (UiAnchor.stretchAll,stretchHorizontal, and any custom anchor like them) silently offset centered/right-aligned/justified text away from the panel it was meant to fill.createUiLayoutEcsSystemnow also syncs a newTextEcsComponent.horizontalAlignPivotfield from the anchor's ownpivot.xfor any stretch-x-anchored label, alongside the existingmaxWidthsync, sohorizontalAlignnow works correctly under any pivot - not just a left one - with no caller-side workaround needed. - rendering: Fix the sprite fragment shader dropping
SpriteEcsComponent.tintColor's alpha component entirely, using only the sampled texture's own alpha - a sprite's final alpha is nowtexture.a * tintColor.a, so tinting a sprite partially transparent (tintColor.a < 1) actually renders it that way instead of silently staying fully opaque. The defaulttintColor.ais1, so this doesn't change how any existing content renders unless it was already (ineffectively) setting a non-1tint alpha.
[0.24.2] - 2026-08-03
Added
- physics: Add
TerrainCollider, a static, non-convex 2D ground collider defined by a heightmap of surface points (rolling hills, a canyon profile, any left-to-right surface with more than one slope), resolved viacreateBroadPhaseEcsSystem/createNarrowPhaseEcsSystemagainstCircleCollider/PolygonColliderbodies exactly like other colliders
[0.24.1] - 2026-08-03
Added
- physics: Add revolute (hinge) and prismatic (slider) joints, via
addRevoluteJointComponent/createRevoluteJointEcsSystemandaddPrismaticJointComponent/createPrismaticJointEcsSystem, constraining two entities together with a warm-started, soft-constraint velocity solve, with optional angle/translation limits - physics: Add
AngularVelocityMotorEcsComponent/createAngularVelocityMotorEcsSystem, driving a single entity's angular velocity towards a target speed within a per-tick torque budget - physics: Add
LinearSpringEcsComponent/createLinearSpringEcsSystemandLinearDamperEcsComponent/createLinearDamperEcsSystem, Hooke's-law and velocity-damping force generators connecting two entities' anchor points, for soft connections like vehicle suspension - physics: Add
applyTorque, a one-shot torque helper mirroringapplyImpulsefor angular-only forces (a thruster, a scripted nudge) - physics: Add
applyExplosiveForce, applying a radial impulse (falling off linearly with distance) to every dynamic body within a radius of a point, for area-effect blasts - physics: Add
angularDragtoRigidBodyEcsComponent, dampingangularVelocitytowards0every tick (applied bycreateEulerIntegrationEcsSystem); defaults to0, so existing bodies are unaffected - fphysics: Add AABB broad-phase and SAT narrow-phase collision detection for
CircleColliderand the newPolygonCollider, viacreateBroadPhaseEcsSystemandcreateNarrowPhaseEcsSystem - fphysics: Add
createCollisionResolutionEcsSystem, a sequential-impulse collision resolver with soft-constraint penetration correction, Coulomb friction, restitution, and warm-starting of accumulated impulses across ticks;ColliderEcsComponentgainsfriction/restitutionproperties
Changed
- physics: Renamed the physics module's source directory from
fphysicsback tophysicsnow that the old physics engine it temporarily coexisted with has been removed; the published@forge-game-engine/forge/physicssubpath is unchanged, but was previously unresolvable (it pointed at adist/physicsoutput that no longer existed) and is now fixed
Fixed
- physics: Fix
createCollisionResolutionEcsSystem's normal-impulse solve dividing its soft-constraintimpulseScalecorrection term by effective mass a second time, making that correction scale with mass² instead of staying mass-invariant. This was unnoticeable for the small test-scale bodies covered by unit tests, but caused massive, growing interpenetration ("sinking"/tunneling through other bodies) for real-world-scale masses, most visibly as balls passing through each other in the Newton's Cradle demo, bricks sinking into each other in the wrecking-ball demo, and the car falling through terrain - rendering: Fix sprites rotating in the opposite direction (mirrored) from their entity's actual
RotationEcsComponent.world/physics rotation.position.world.yis negated when uploaded to the sprite shader (to convert the engine's Y-up world space to the shader's Y-down space), but rotation wasn't given the same treatment, so a positive rotation visually spun the sprite the wrong way relative to its (correctly rotating) collider. This only became visible once colliders had a distinguishable, non-symmetric shape (e.g. a rotatingPolygonCollidertriangle) to reveal the mismatch - rendering: Fix
SpriteEcsComponent.pivotapplying only half of its intended offset (e.g.pivot: (0, 0)landed 25% in from the sprite's edge instead of at the edge, and a pivot matching a rotated collider's centroid left the sprite visibly adrift from its own collider - gapping off the ground on one side, sinking into it on another - as the entity rotated). A rendering-pipeline change had widened the vertex quad's coordinate range without widening the pivot term to match; nine-slice sprites were unaffected since they always render their regions with a centered pivot - common: Fix
Time.updateclamping its very first call's delta (the gap between the timestamp's arbitrary clock offset, e.g. arequestAnimationFrametimestamp measured from page navigation start, andTime's0baseline) against the same oversized-delta guard meant for mid-simulation hitches, permanently offsetting every subsequenttimeInMilliseconds/timeInSecondsvalue behind the real elapsed time
[0.24.0] - 2026-07-27
Added
- rendering: Add
preserveDrawingBufferoption tocreateRenderContext/RenderContext, for consumers that need to read the canvas's pixels back (e.g.toDataURL,drawImage) after a frame has already been presented - rendering: Add
calculateVisibleWorldSizeto compute the width/height, in world units, a camera's view spans at given destination dimensions, so game logic can size and position things relative to what's actually visible instead of reading canvas pixel dimensions directly
Changed
- rendering: Cameras now show a fixed number of vertical world units (
verticalWorldUnits, default10) regardless of screen resolution or aspect ratio, replacing the old implicit 1-world-unit-equals-1-pixel behavior. This is a behavior change: existing sprite and physics sizes will render at a different scale until re-tuned to the new default
Fixed
- input: Fix
MouseInputSourcetriggeringTriggerActionbindings regardless of the active input group, ignoringInputManager's active-group gating thatKeyboardInputSourceand every other action type already respect - demos: Fix the linear-spring-damper, revolute-joint, torque, physics, prismatic-joint, car, wrecking-ball, and Newton's cradle demos naively non-uniformly stretching bordered sprites (ropes, doors, flywheels, walls, cradle frame) instead of nine-slicing them, and tinting already-colored artwork; the wrecking-ball and Newton's cradle demos also now render each ball's crane/pendulum arm, rotating correctly as it swings
[0.23.1] - 2026-07-24
Fixed
- rendering: Fix nine-slice sprites rendering their top/bottom border and corner regions vertically flipped relative to their left/right regions, causing distorted or misaligned edges
- rendering: Fix nine-slice sprites tearing apart when rotated, instead of rotating as one intact sprite
[0.23.0] - 2026-07-23
Added
- Add LinearSpring and LinearDamper for soft body connections
- rendering: Add nine-slice support for sprites
- demos: Add car demo
- physics: Add TerrainShape for 2D heightmap terrain
[0.22.2] - 2026-07-20
Added
- Add pixelated filtering option for createImageSprite
- Add torque application and angular-velocity motor ECS systems
[0.22.1] - 2026-07-17
Added
- Add Prismatic Joint implementation and demo
- Enhance
GamepadInputSourceto support hot-plugging and last-connected gamepad selection - Add Wrecking Ball demo and implement RevoluteJoint functionality
[0.22.0] - 2026-07-14
Changed
- Replace the custom shader include syntax with a
#pragma-based shader pre-processing pipeline
[0.21.1] - 2026-06-24
Added
- Add fullscreen to demos
[0.21.0] - 2026-06-13
Added
- Implement physics simulation with RigidBody and PhysicsWorld
- Add force application API to the physics engine
Changed
- Add a physics demo showcasing
RigidBodyandPhysicsWorld
[0.20.1] - 2026-06-09
No user-facing changes.
[0.20.0] - 2026-06-02
Added
- ecs: Add ECS core components, systems, and world implementation
Changed
- Add sprite-sheet creation and animation frame-selection utilities, and simplify the animation demo
[0.19.1] - 2026-04-19
Added
- render-context: Implement resize method and update viewport handling
[0.19.0] - 2026-04-18
Fixed
- rendering: Flip Y axis in screenToWorldSpace
[0.18.0] - 2026-01-17
Added
- BREAKING: Replace the ECS implementation with a new architecture, and migrate all built-in components and systems to it
[0.17.7] - 2026-01-03
Added
- Add shooting to spaceshooter demo
[0.17.6] - 2026-01-01
Changed
- Refine animation input types and add lifecycle hooks to the ECS
System/Worldclasses
[0.17.5] - 2025-12-28
Fixed
- Update import paths to include file extensions
[0.17.4] - 2025-12-28
No user-facing changes.
[0.17.3] - 2025-12-27
Fixed
- Update default canvas dimensions to use container size
[0.17.2] - 2025-12-27
Fixed
- Update import paths to include file extensions
[0.17.1] - 2025-12-27
Added
- Export render context from index
[0.17.0] - 2025-12-27
Added
- Add render context
[0.16.0] - 2025-12-20
Added
- Adds tint to sprites
Fixed
- Fix camera zoom
[0.15.3] - 2025-12-14
Fixed
- Update import path for Transition in AnimationTransition.ts
[0.15.2] - 2025-12-14
Added
- Add finite state machine exports to package.json
[0.15.1] - 2025-12-14
Added
- Implement resizing for render layers and add finite state machine exports
Changed
- Simplify the sprite animation system and demo integration
[0.15.0] - 2025-11-01
Changed
- Rename
SoundComponenttoAudioComponentand refine the audio system
[0.14.18] - 2025-11-01
Added
- Add BooleanModelProperty type for boolean value representation
[0.14.17] - 2025-10-27
Fixed
- Change error throw to console warning when adding existing system
[0.14.16] - 2025-10-27
Fixed
- Update RenderSystem constructor to use layer name in super call
[0.14.15] - 2025-10-27
Fixed
- Correct system names in constructors for ResetInputSystem and UpdateInputSystem
[0.14.14] - 2025-10-27
No user-facing changes.
[0.14.13] - 2025-10-24
Added
- Add .js extensions to all imports for ESM compliance
[0.14.12] - 2025-10-22
Added
- Add error handling for duplicate components and systems in Entity and World classes
[0.14.11] - 2025-10-21
Added
- Implement recursive entity removal in World class
[0.14.10] - 2025-10-21
No user-facing changes.
[0.14.9] - 2025-10-21
No user-facing changes.
[0.14.8] - 2025-10-21
Added
- Export transform-system from index.ts
[0.14.7] - 2025-10-20
Added
- Convert GLSL files to JavaScript modules in dist for tool-agnostic build
Fixed
- Include shader files in package after switching to tsc build
[0.14.6] - 2025-10-19
Fixed
- Update import paths to use index.js for module exports
[0.14.5] - 2025-10-19
No user-facing changes.
[0.14.4] - 2025-10-18
No user-facing changes.
[0.14.3] - 2025-10-17
Fixed
- Resolve duplicate type definitions in package build
[0.14.2] - 2025-10-16
No user-facing changes.
[0.14.1] - 2025-10-15
Added
- Add the
commonmodule to package exports
[0.14.0] - 2025-10-15
Added
- Add parent-child relationship support for entities
- Add multiple entry points to reduce bundle size
- Add optional parent and enabled parameters to Entity constructor and buildAndAddEntity via EntityOptions
Changed
- Move age/scale features into a new
lifecyclemodule
[0.13.3] - 2025-10-08
Added
- Added getEntityById to world.ts
[0.13.2] - 2025-09-30
Added
- math: Implement radiansToVector function and add tests
[0.13.1] - 2025-09-30
No user-facing changes.
[0.13.0] - 2025-09-20
Added
- math: Add
radiansToDegreesandsignedSquarefunctions with tests, and implementsmoothDampVector2for position interpolation
[0.12.1] - 2025-09-19
Fixed
- Make the sprite pivot use a new vector in the constructor
[0.12.0] - 2025-09-19
Added
- input: Add mouse input support with 2D axis bindings and cursor value types
[0.11.0] - 2025-09-18
Added
- input: Add 1D and 2D axis input bindings and integrate into keyboard input source
[0.10.0] - 2025-09-18
Added
- Add keyboard axis input
[0.9.0] - 2025-09-16
Added
- Add spritesheet animations to entities
- Add particle system with configurable emitters
Fixed
- Update sprite vertex shader to normalise the pivot, and apply the pivot before scaling
[0.8.0] - 2025-07-25
Added
- Add input feature
[0.7.0] - 2025-07-06
Changed
- Performance enhancements
[0.6.0] - 2025-06-30
Added
- Add seconds api to time object
[0.5.10] - 2025-06-28
Fixed
- Correct camera position adjustments in CameraSystem and RenderSystem
[0.5.9] - 2025-06-26
No user-facing changes.
[0.5.8] - 2025-06-22
No user-facing changes.
[0.5.7] - 2025-06-20
Added
- Add world initialization to System and update World to call initialize on added systems
Fixed
- Update sprite creation to use
renderLayers[0]instead offoregroundRenderLayer.layer
[0.5.6] - 2025-06-16
No user-facing changes.
[0.5.5] - 2025-06-15
Changed
- Update scene design
[0.5.4] - 2025-06-08
No user-facing changes.
[0.5.3] - 2025-06-08
No user-facing changes.
[0.5.2] - 2025-06-08
No user-facing changes.
[0.5.1] - 2025-06-08
Added
- Add a Docusaurus documentation site
[0.5.0] - 2025-06-08
Added
- Add docs
[0.4.13] - 2025-06-02
No user-facing changes.
[0.4.12] - 2025-05-27
No user-facing changes.
[0.4.11] - 2025-05-26
No user-facing changes.
[0.4.10] - 2025-05-26
No user-facing changes.
[0.4.9] - 2025-05-26
No user-facing changes.
[0.4.8] - 2025-05-24
No user-facing changes.
[0.4.7] - 2025-05-24
Fixed
- Ensure correct blending function is set during rendering
[0.4.6] - 2025-05-24
Fixed
- Pass WebGL context to beforeBind method for improved customization
[0.4.5] - 2025-05-23
Fixed
- Improve entity management in updateSystemEntities method
[0.4.4] - 2025-05-23
Fixed
- Fix a bug where a system would not pick up an entity when a matching component was added after the entity was already registered in the world
[0.4.3] - 2025-05-21
Added
- Added hexagon, triangle, octagon, rhombus, trapezoid
[0.4.2] - 2025-05-20
Fixed
- Change listener type from
Promise<void>tovoid
[0.4.1] - 2025-05-19
Added
- Time: Add fps getter to return current frames per second
Changed
- Add a Rive data-binding example scene to the demo app
[0.4.0] - 2025-05-14
Added
- Added raycast
[0.3.5] - 2025-05-10
Fixed
- Ensure canvas styles match dimensions on creation
[0.3.4] - 2025-05-10
No user-facing changes.
[0.3.3] - 2025-05-08
Added
- Add matterjs
[0.3.2] - 2025-05-05
Added
- Refactor createSprite to use Material and add createImageSprite utility
- Add createScene utility
- Refactor createShipPilotScene and createScene
- Update createScene and createSprite functions to return types
[0.3.1] - 2025-05-05
Fixed
- Added sdfBoxInclude to shaderstore
[0.3.0] - 2025-05-05
Added
- Added sdf box shader
[0.2.16] - 2025-05-04
No user-facing changes.
[0.2.15] - 2025-05-04
No user-facing changes.
[0.2.14] - 2025-05-04
No user-facing changes.
[0.2.13] - 2025-05-04
No user-facing changes.
[0.2.12] - 2025-05-04
Added
- Implement CircleCollider class with containment checks and bounding box calculations
[0.2.11] - 2025-05-03
Added
- Enhance Color class to support RGBA and update related tests
- Add Float32Array conversion methods for Color and vector classes
[0.2.10] - 2025-05-02
Added
- Export color module from rendering index
[0.2.9] - 2025-05-01
Added
- Add create-shader-store utility for managing shader includes and shaders
[0.2.8] - 2025-05-01
No user-facing changes.
[0.2.7] - 2025-04-30
Added
- Export shaders module in rendering index
[0.2.6] - 2025-04-30
Added
- Refactor addForgeRenderLayers to use LayerDetail type for clarity
[0.2.5] - 2025-04-30
Added
- Support for registering and deregistering multiple updatable and stoppable objects in Scene
- Add functions to create and register multiple Forge render layers with the layer service
[0.2.4] - 2025-04-29
Added
- Add addInputs function and update exports in utilities
- Add buildAndAddEntity method to World class for entity creation
- Add addForgeRenderLayer and addRiveRenderLayer functions for rendering layers
- Remove Camera class and add addCamera utility function for camera setup
- Add createSprite function and update exports in utilities
- Remove unused Stoppable type import from RiveRenderLayer
- Implement Stoppable interface in RiveRenderLayer class
Fixed
- Correct canvas ID string in addForgeRenderLayer function
[0.2.3] - 2025-04-28
Added
- Add Vector3 class with basic vector operations and tests
- Add gradient image for enhanced visual presentation
- Implement gradient material and update Perlin noise scene integration
[0.2.2] - 2025-04-06
Added
- Enhance resolveIncludes to handle nested and circular includes
- Enhance resolveIncludes to support resolved variables and prevent duplicate declarations
- Add Perlin noise scene and material with shader includes for rendering
- Implement Perlin noise rendering with shader includes and update random gradient function
- Import Perlin noise scene in game module and update scene exports
[0.2.1] - 2025-04-05
Added
- Implement star system and components, refactor starfield handling in ship pilot scene
- Implement resolveIncludes function for shader include handling and add tests
Fixed
- Add definite assignment assertion for velocity in StarComponent
- Improve error messages for shader syntax validation in resolveIncludes
[0.2.0] - 2025-04-05
Added
- Implement ObjectPool class with basic functionality and tests
[0.1.3] - 2025-04-04
No user-facing changes.
[0.1.2] - 2025-04-04
Changed
- Migrate from @rive-app/canvas to @rive-app/webgl
[0.1.1] - 2025-03-30
Initial public release. This release establishes the foundation of the engine, assembled from the project's earliest prototyping.
Added
- WebGL2-based rendering pipeline, including sprite shaders, sprite batching, materials, and camera zoom/panning
- Core UI layer: hoverable components, a grid layout system, and a button/sprite factory
- Rive integration for interactive vector animations, with resize/stop/reload controls on
RiveRenderLayer PolygonColliderandMatrix3x3as initial physics/math primitivesImageCachefor asset loading, later migrated into the asset-loading module- Ship and starfield entities/components for the initial demo scene
Fixed
- Magenta flashing in sprite debug mode, replaced with predefined debug colors
- Box collider calculation to require at least 2 points
- Rounded rectangle rendering line-width handling
- Camera translation and panning direction in
CameraSystem