Version Packages - #7
Merged
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.
Releases
@ignifx/2d@0.2.1
Patch Changes
@ignifx/3d@0.2.1
Patch Changes
7adf987: The look rigs re-arm pointer lock, ignore an unlocked mouse, and read a stick as a rate
FirstPersonControllerandThirdPersonCamerawere each doing their own look arithmetic, and bothwere wrong in the same four ways. The look half of both now lives in one internal module and the
four defects are fixed together.
Pointer lock re-arms.
FirstPersonControllerset a latch on its first request and never clearedit, so once the browser released the lock — Escape, which the templates also bind to the pause menu,
or a focus change — no later click could take it back and the player finished the session with an
unlocked mouse. The lock is now requested on every frame that carries a
pointerdownwhile the lock is not held; the only guard is a request already in flight. A refusal is logged and
dropped, exactly as before.
An unlocked mouse no longer turns the view. While
lockPointerOnClickis on and the lock is notheld, a
Lookreading whoseInputAction.activeDeviceisMouseorPointeris ignored. Before,the view spun whenever the cursor crossed the canvas — on its way to a menu button, say — and stopped
dead when the cursor left the frame. Gamepad, touch, and virtual sticks are never gated: they have no
cursor to lose.
lockPointerOnClick: falsekeeps unconditional mouse look for drag-to-look designs.A stick is a rate, a pointer is a displacement. Both rigs did
yaw += look.x * sensitivityforevery device. That is right for a pointer delta, which is this frame's motion, and wrong for a stick,
whose value is a deflection: at 120 Hz the stick turned twice as fast as at 60 Hz, and games papered
over it with a
scale(18)processor. A gamepad or virtual reading is now multiplied by the newstickLookSpeed(degrees per second at full deflection, default 180) and the frame delta, so thesame push turns through the same angle in the same second at any frame rate, and slow motion slows
the look with everything else.
sensitivitykeeps its meaning for pointer readings — degrees per unit,which with
@ignifx/input's CSS-pixel deltas is degrees per CSS pixel, so 0.08–0.15 suits most miceon every display and at every render scale.
Up is up on every device. A screen's
movementYgrows downward and a stick'sygrows upward,and both rigs subtracted
look.yfrom their pitch regardless — so oneinvertYcould only ever suitone device family, and a first-person mouse looked down when it was pushed forward. The pitch axis
is normalised where the device is known:
Mouse,Pointer, andTouchreadings are negated, gamepadand virtual are not, and the rigs apply their own sign and
invertYon top as before. Moving themouse forward and pushing a stick up now both look up in first person, and both lower the boom and
aim the camera up in third person;
invertYflips every device together.ThirdPersonCameraalso gainslockPointerOnClick, defaulting tofalse: a rig that orbits on aheld mouse button needs its cursor, so nothing changes for an existing third-person game until it
opts in to the console-style "click to capture, Escape to release" behaviour.
Public API change:
FirstPersonController.stickLookSpeed,ThirdPersonCamera.stickLookSpeed, andThirdPersonCamera.lockPointerOnClickare new schema fields. No field was removed or renamed. Twobehaviour changes need a look from any game that ships a look binding: drop the
scale(...)processors from stick look bindings, which double-count against
stickLookSpeed, keeping thedeadzone(...); and drop anyscale(1, -1)that existed to line a stick up with the mouse, becausethe rigs agree on which way is up now — reach for
invertYinstead.6a39fae:
shapeCastcan sweep past one body, and the third-person boom and step probe no longer report the character's own capsuleLite's
ShapeCastQuerycarries no collision masks — unlikephysicsRaycast— so a shape sweepcannot be filtered by layer inside Havok;
layerMaskonly ever decided which hit was attributedan entity, and a body outside the mask still stopped the sweep, reported with
entity: null. Twocallers in
@ignifx/3dwere sweeping from inside the character's own capsule and were stopped by itat fraction zero every time:
ThirdPersonCamerasweeps from its shoulder pivot, which sits inside the capsule, out along theboom. For every yaw whose boom crossed the capsule the sweep hit it immediately,
currentDistancecollapsed to
0and the camera sat inside the character's head — measured on 2026-09-08 in thethird-person template at every yaw from 30° to 180° at the spawn, with no wall anywhere near.
collisionLayers: ["Level", "Prop"]could not prevent it, because the mask never reached the sweep.ThirdPersonController's step probe sweeps a sphere forward from the character's feet; both of itssweeps found the capsule first, so
stepHeightnever lifted anything.ShapeCastOptions—QueryOptionsplusignore?: Entity | null— names the one body the sweeppasses through, resolved to its Havok body whether the entity is a
Rigidbody, a collider-onlystatic, or a
CharacterController(whose capsule body Lite exposes throughgetBody()). The camerarig ignores its
target; the step probe ignores its own entity. A masked-out body other than theignored one still shortens the boom, which for scenery is the point and is now said so on
collisionLayers.What a game author sees: a third-person camera that holds its distance through a full orbit and a
stepHeightthat climbs steps; and, for their own queries, a way to sweep out of a body they arestanding in.
Public API change:
ShapeCastOptionsis new andPhysicsService.shapeCasttakes it in place ofQueryOptions(a widening — every existing call compiles). Nothing was removed or renamed.ef054ec:
ThirdPersonCamerano longer rolls the horizon when the mouse moves sidewaysThe rig built its rotation with one
Quat.fromEulerDegrees(pitch, yaw, 0)call. That helper composesin intrinsic XYZ order, so the boom was pitched first and then yawed about the tilted axis: with
any pitch at all — and the template starts twelve degrees down — a horizontal mouse motion tilted the
camera rather than turning it, and the camera rose and fell as it went round (measured 2026-09-08:
at 15 degrees of pitch its height followed
cos(yaw)).The rotation is now
Ry(yaw) * Rx(pitch): yaw about the world's up, then pitch about the camera'sown right, which is the one order in which looking sideways is a turn about the vertical. The
starting angles are read off the entity's forward vector at
awakeinstead of its Euler angles, soan authored downward tilt seeds the same pitch whatever Euler order wrote it.
What a game author sees: a third-person camera whose horizon stays level and whose height stays put
through a full orbit, at every pitch.
Public API change: none.
Updated dependencies [21a4ba7]
Updated dependencies [6a39fae]
Updated dependencies [388b0f6]
Updated dependencies [6a39fae]
@ignifx/audio@0.2.1
Patch Changes
@ignifx/core@0.2.1
Patch Changes
388b0f6:
PostProcessStackfields are live: a slider bound tobloom.thresholdnow changes the pictureThe stack built its chain once, the first time any effect was enabled, and every
syncafter thatonly flipped the chain's
executionEnabledto match the component. The bloom and SMAA records wereread at that one moment and never again, so
post.bloom.threshold = 0.4on a running game didnothing — the website's bloom example had four sliders and none of them moved the frame — and
post.bloom.enabled = falseon a one-effect chain left bloom running, because the chain's identitywas checked only while no chain existed yet.
Lite's
BloomPostProcessTaskexposesweight,kernel,thresholdandexposureas writablefields and
updateUniforms()re-uploads every sub-pass from them;SmaaPostProcessTaskdocumentsthe same for
threshold,maxSearchSteps,diagonalDetectionandcornerDetection(verifiedagainst
@babylonjs/lite@1.27.0,index.d.ts1287 and 11622). The chain now keeps a typed handleto each of those tasks and, once per frame, uploads whatever changed since the last upload — a frame
in which nothing moved uploads nothing. The one tuning Lite fixes at creation is bloom's
bloomScale, which sizes the blur targets, sobloom.scalejoins "which effects, in which order"in the chain's identity: a change to any of them disposes the old chain and records a new one. Lite
cannot remove a task from a frame graph, so the old tasks stay in it, disabled and with their GPU
resources freed, at one branch per frame each — toggling an effect's
enabledin a settings menucosts a rebuild per click, whereas toggling the whole component's
enabledkeeps the chain andskips it, which is what the templates' settings screens do.
What a game author sees: an inspector edit, a settings slider or a script write to any bloom or SMAA
field takes effect on the next frame, and turning a single effect off actually turns it off.
Public API change: none.
PostProcessChain.applySettingsand the two adapter functions it calls are@internal.@ignifx/devtools@0.2.1
Patch Changes
@ignifx/electron@0.2.1
Patch Changes
ignifx@0.2.1
Patch Changes
@ignifx/input@0.2.1
Patch Changes
21a4ba7: Pointer deltas are CSS pixels, the canvas wheel no longer scrolls the page,
InputAction.activeDevice, and pointer lock asks for raw mouse motionFour fixes to the things a look control is built out of, all found while chasing "the camera on the
third-person example does not behave correctly" and "scroll interferes with page scrolling".
The wheel is taken non-passively. The canvas
wheellistener was registered{ passive: true },which forbids
preventDefault, so a wheel over a running game zoomed the camera and scrolled thepage underneath it — obvious on ignifx.com, where the examples live in an
<iframe>and the wheeltook the article with it. The listener is now non-passive and prevents the default on every event it
queues. The wheel is still read from the canvas alone, so a wheel over the page's own chrome is
untouched, and the queued entry is unchanged.
Pointer deltas are CSS pixels; positions stay backing-store pixels.
<Mouse>/delta,<Pointer>/delta,<Touch>/…/deltaandevent.deltaX/deltaYwere multiplied bycanvas.width / rect.width, the same scale positions need. A delta is hand motion, not a place onthe render target: the scale doubled every look sensitivity on a device-pixel-ratio-2 display and
moved it again whenever a settings screen changed
renderer.resolutionScale. Deltas are now thebrowser's raw
movementX/movementY, and for the pointer types that leave those at zero (touch,some pens) the DOM adapter derives the motion from that pointer's own successive
clientX/clientY— in CSS pixels, never from the queued position.
DeviceWriterderives nothing at all now, sosimulateEventreports exactly the delta a test states. Positions are unchanged, sorenderer.pickAsync(pointer.position)andCamera.screenToRayare still exact at every ratio.A game author sees one number to tune: a mouse look sensitivity in degrees per CSS pixel, around
0.08–0.15, the same on every display.
InputAction.activeDevicenames the device family behind the binding whose magnitude won theframe, and
nullwhen the action is at rest or disabled — stable for the frame like every otherreading. One
Lookaction bound to both a mouse and a stick carries two different quantities, andthis is what lets a rig tell them apart:
@ignifx/3duses it to ignore mouse look until the pointeris locked and to read a stick as a rate. A composite reports the device of its first part, which is
the only sensible answer for a
2DVectorwhose four parts are one device.PointerLock.request()asks forunadjustedMovement: truebefore it asks plainly, falling backwhen the browser rejects the option by throwing or by rejecting the returned promise. That is raw
mouse motion with the desktop's pointer-acceleration curve removed, which is what a first-person look
wants — with acceleration on, a fast flick turns further than a slow one over the same desk distance,
which is most of what players describe as a jumpy look. Settle semantics are unchanged:
trueonpointerlockchange,falseonpointerlockerror,IGX-0809on a headless app.Public API change:
InputAction.activeDevice: DeviceKind | nullis new. No signature changed, butthe meaning of
<Mouse>/delta,<Pointer>/deltaand<Touch>/…/deltadid: they are CSS pixelsnow, so a project that tuned its sensitivity on a retina display re-tunes it once (
invertandscale(...)processors still apply as before).Updated dependencies [388b0f6]
@ignifx/physics@0.2.1
Patch Changes
6a39fae: The interpolated display pose is written before
update, so camera rigs and scripts see the pose the frame drawsPoses are still snapshotted on the fixed step and interpolated with
time.fixedStepAlpha, but thesystem that writes
lerp(prev, cur, alpha)into aRigidbody's orCharacterController's nodemoved from
Systems(PreRender, −500)toSystems(Update, −900)— straight after the fixed loop andlifecycle flush B, in front of
scripts.update.Why:
ThirdPersonCamera,FirstPersonController, bone attachments and every hand-written follow camerarun in
update/lateUpdate, which used to be before the display pose existed. They framed thecharacter where the last fixed step left it while the renderer drew it at
lerp(prev, cur, alpha),so at any refresh rate the character juddered against the camera by up to one fixed step of motion —
about 0.1 m at sprint speed, every frame. Writing the pose at the top of
Updatemeansupdate,lateUpdate, animation, camera rigs and the render sync all read the same pose the frame presents.This is Unity's model.
What a game author sees: a smooth third- or first-person camera, and
transform.positionread fromupdate/lateUpdatenow returning the interpolated pose rather than the last fixed one. Thesimulation is unchanged: the restore system at
FixedUpdate −100still runs beforescripts.fixedUpdate, sofixedUpdateand Havok only ever see authoritative poses, and a kinematicbody moved by writing its transform behaves exactly as before — the restore already overwrote such a
write before the step read it. A system that must read an authoritative pose outside the fixed loop
registers at
Phase.Updatewith an order below−900.Public API change: none. The order constant and the interpolation system are
@internal.6a39fae:
shapeCastcan sweep past one body, and the third-person boom and step probe no longer report the character's own capsuleLite's
ShapeCastQuerycarries no collision masks — unlikephysicsRaycast— so a shape sweepcannot be filtered by layer inside Havok;
layerMaskonly ever decided which hit was attributedan entity, and a body outside the mask still stopped the sweep, reported with
entity: null. Twocallers in
@ignifx/3dwere sweeping from inside the character's own capsule and were stopped by itat fraction zero every time:
ThirdPersonCamerasweeps from its shoulder pivot, which sits inside the capsule, out along theboom. For every yaw whose boom crossed the capsule the sweep hit it immediately,
currentDistancecollapsed to
0and the camera sat inside the character's head — measured on 2026-09-08 in thethird-person template at every yaw from 30° to 180° at the spawn, with no wall anywhere near.
collisionLayers: ["Level", "Prop"]could not prevent it, because the mask never reached the sweep.ThirdPersonController's step probe sweeps a sphere forward from the character's feet; both of itssweeps found the capsule first, so
stepHeightnever lifted anything.ShapeCastOptions—QueryOptionsplusignore?: Entity | null— names the one body the sweeppasses through, resolved to its Havok body whether the entity is a
Rigidbody, a collider-onlystatic, or a
CharacterController(whose capsule body Lite exposes throughgetBody()). The camerarig ignores its
target; the step probe ignores its own entity. A masked-out body other than theignored one still shortens the boom, which for scenery is the point and is now said so on
collisionLayers.What a game author sees: a third-person camera that holds its distance through a full orbit and a
stepHeightthat climbs steps; and, for their own queries, a way to sweep out of a body they arestanding in.
Public API change:
ShapeCastOptionsis new andPhysicsService.shapeCasttakes it in place ofQueryOptions(a widening — every existing call compiles). Nothing was removed or renamed.Updated dependencies [388b0f6]
@ignifx/physics-2d@0.2.1
Patch Changes
6a39fae: A
CharacterController2Dwalks through triggers instead of bouncing off them, and the display pose is written beforeupdateso follow cameras stop judderingTriggers were walls. Rapier's
KinematicCharacterController.computeColliderMovementtakesfilterFlagsas its third argument, and the adapter passedundefined, which leaves sensors in theobstacle set. Measured on 2026-09-08 against
@dimforge/rapier2d-compat@0.20.0(macOS arm64,Node 24): a kinematic box driven by the controller towards a static sensor ball stops dead at the
sensor's surface —
x = 0.49for a sensor atx = 1with radius 0.3, a 0.2 half-width character anda 0.01 offset — and because it never overlaps,
drainCollisionEventsreports nothing at all. WithQueryFilterFlags.EXCLUDE_SENSORSthe same character reachesx = 3.0and the queue reports thestarted/stoppedpair. Every controller move now passes that flag.What a game author sees: a collectible finally works. The side-scroller template's coins are a
CircleCollider2D { isTrigger: true }on an entity with no rigidbody, and the player is aCharacterController2D— so every coin was an invisible bump the player stopped against andonTriggerEnternever fired. The same was true of the top-down template's shrine pads and of anytrigger zone a controller-driven character is meant to enter. The flip side is intended: a sensor no
longer appears in
CharacterController2D.onCollided, because a trigger is not an obstacle. UseonTriggerEnter/onTriggerExitfor pickups and zones andonCollidedfor the walls and floors thecharacter actually pushed against.
The display pose is written at the top of
Update, not inPreRender. Poses are stillsnapshotted on the fixed step and interpolated with
time.fixedStepAlpha, but the system that writeslerp(prev, cur, alpha)moved fromSystems(PreRender, −500)toSystems(Update, −900), which runsstraight after the fixed loop and before
scripts.update.Camera2DFollowand every hand-writtenfollow camera run in
update/lateUpdate, so they used to frame the character where the last fixedstep left it while the renderer drew it interpolated: up to one fixed step of relative motion — about
0.1 m at sprint speed — of judder every frame, and with the pixel-perfect camera the two poses are
quantised independently, so the sprite visibly shook while running. Now scripts, animation, camera
rigs and the render sync all read the pose the frame presents, which is Unity's model.
fixedUpdateand Rapier are unaffected: the restore system at
FixedUpdate −100still runs beforescripts.fixedUpdate, so the simulation only ever sees authoritative poses, and a kinematic bodymoved by writing its transform behaves exactly as before.
Public API change: none. The order constants and both systems are
@internal.Updated dependencies [388b0f6]
@ignifx/ui@0.2.1
Patch Changes
@ignifx/vite-plugin@0.2.1
Patch Changes
@ignifx/cli@0.2.1
No changes in this release.