Portals Avatars Changelog
Every release of the Portals avatar SDK is archived immutably and can be pinned from portals.json with { "guardiansSdk": "x.y.z" }. This page lists what each version changed. Versions that are not listed were never released: the number was skipped, so pinning it fails the publish.
Names in backticks are exports or options of @portals/avatars; see the Portals Avatars guide for how to use them. A version listed with two entries was cut on two parallel branches on the same day; the first version that contains both is called out below it.
0.59.0 — 2026-10-09
An emote's prop and sound, with no game code.
- An emote file's prop rides on its rig bone while the emote plays, scaled in and out with it and moved by the file's own clips, at the size and shape it has in the file on either body.
emotePropBudget(default 80,000 triangles in 64 draw calls) caps what props cost across every avatar: the player's own first, then the rest by distance; past it an avatar's emote plays without its prop.avatar.emotes.propCost(),setPropsAllowed()andisShowingProp()are the handles a host with its own avatars uses. - An emote file's sound starts with its
Emoteclip and runs on the emote's clock: it loops with a looping emote and ends with the loop, and a one-shot's plays through. A late join, a hitch or a hidden tab puts it back where the emote is. - Every sound plays through
avatars.audio, anEmoteAudioBus. Each one is measured on the player's device (ITU-R BS.1770-4) and played at -16 LUFS under a -1 dBTP ceiling, whatever the file's level. Other players' sounds are positional, at half the player's own volume (setRemoteVolume()), and only the nearest three are heard at once.setVolume()is the game's level andsetMuted()pauses them all; passaudio: { context, destination }to route emote sounds into the game's own mixer. They go silent while the page is hidden, and a sound that would decode to more than the standard allows is never decoded. - The bus follows the player's Portals emote-sound setting — everyone's, only their own, or none — through
Portals.preferences(Portals SDK 1.22.0), and ducks 12 dB whenever someone else talks inPortals.voice.createController()makes its avatar the player's own (emotes.setLocal()) and hears from its camera;syncEmotes()marks itslocalavatar too. avatars.getEmoteStats()reports props shown and held back and sounds playing and heard.EMOTE_PROP_LIMITS,EMOTE_SOUND_LIMITS,measureLoudness()andemoteSoundGain()are exported.
0.58.0 — 2026-10-09
Emotes in multiplayer.
syncEmotes({ net, local, avatarFor })shares the player's emotes overPortals.netand plays every other player's on the avatar you give for them. A player can only show what Portals vouched for when they joined (player.emoteson the roster, Portals SDK 1.21.0); a room from before it allows the starter set only.- A looping emote is announced again every 4 s (
heartbeatMs), so a late joiner sees it already under way. - Its messages carry
$portals: EMOTE_SYNC_MESSAGE, so a game's own handler can tell them apart.
0.57.0 — 2026-10-09
Emotes, with no game code: every player's emote wheel, a full-body emote layer, and the emote standard.
avatar.emotesholds the wheel (list(),find(),setLoadout()) and plays from it (play(),stop(),current(),isPlaying(),preload(),on('start' | 'end' | 'loadout')). An emote plays over whatever animates the body and fades back to it;elapsedjoins one already under way.createAvatarFromPlayer()loads the player's wheel fromplayer.avatar.emotes(Portals SDK 1.20.0). Every other avatar starts with the starter set: wave, clap, shrug, dance and celebrate.createController()draws a default wheel — B, a touch button, slot numbers — and cancels an emote when the character moves, jumps, crouches, uses an item or leaves the ground.emotes: falseturns emotes off;setEmotesEnabled(),playEmote(),stopEmote(),canEmote()andopenEmoteWheel()are the game's handles. First person pulls back to third for an emote.emotes: { anchor }opens the wheel over an element instead of the middle of the view, kept wholly in view.- An emote whose declaration says
feet: "planted"holds each foot where it stood when it began, bending the legs to reach it, so a standing emote never slides over any idle. Every starter emote that plays its legs is planted. - One file plays alike on both bodies. As it loads, its keys are carried onto each body's own joints (the two bodies' finger joints rest about axes up to 130° apart, so a fist would open out on one), and where its palms meet on the rig it was authored on, the body's arms reach so they meet the same way (her shoulders are narrower, so a clap would pass her hands through each other). Nothing runs per frame.
- Wearables bound to the
emotetrigger now play for every emote, not only the SDK's own emote clips. EmoteController,EmoteWheel,parsePortalsEmote(),STARTER_EMOTESand theEMOTE_*constants (EMOTE_FEETamong them) are exported for hosts that build their own avatars. The wheel shows each emote'sicon(the starter emotes have their own) and loads it through the managed asset path, so it shows in a published game.- Emote files must be on the Portals avatar rig; any other rig is skipped rather than retargeted.
0.55.2 — 2026-10-07
The default third-person camera frames the avatar on the left, and movement starts without waiting for walking or running clips to download.
shoulderOffsetdefaults to-0.45. Explicit offsets keep their existing meaning: zero centres the avatar and positive values frame it on the right.createAvatarprepares idle, walk, run and sprint alongside the default outfit before resolving. All walk and run directional clips are prepared too; optional behaviours and face clips remain lazy.- Locomotion blends in over 80 ms when leaving idle or crouch idle, keeping the legs responsive to the first step. Transitions between moving states keep their 200 ms blend.
0.55.1 — 2026-09-30
Hair trimming preserves the hair beneath a hat's edge, avoiding the bald patches caused by removing a whole face when one corner poked through.
- Every wearable category can select show, trim or hide in the Shop item editor, including hooded tops and robes. Explicit
trimHairortrim_hair=truealso trims hooded full-body outfits that occupy the hat slot; unmarked head-covering costumes still hide all hair by default. - Intersecting hair faces are cut against the hat's radial coverage. Their valid portions remain, including when contact is inside a face with no protruding corner. The real scalp still prevents a buried hat from cutting hair.
- Hooded skinned garments trim against their head region, leaving torso and sleeve surfaces out of the covering. Rigid garments use the actual scalp edge to exclude surfaces below the head.
- New boundary vertices preserve UVs, colors, normals, tangents and morph attributes. Removing a hat or changing hair styles restores the original geometry exactly; wearers own independent trimmed buffers.
- Fits are cached by geometry and placement, including the scalp and hat transforms. The cache is bounded to eight fits per source hair geometry. Clipping is calculated on equip or style changes, with no per-frame clipping work.
0.55.0 — 2026-09-30
Hats can trim the hair under them instead of hiding it all.
WearableDef.trimHair, or the Portalstrim_hairflag on an item's model URL, hides only the hair that would poke through the item while it is worn. The hair it sits over and the hair that falls out below it stay. Seen from the middle of the scalp, a hair vertex pokes through when the item's surface lies between them, or within 2 mm beyond it, outside the scalp; every hair triangle with such a vertex is left out of that avatar's hair. Where an item sinks into the head it doesn't show, and the hair over it stays.- It is worked out when the item goes on, once for each hair style and set of trimming items, and shared by every avatar wearing them: nothing runs per frame. Only this avatar's index changes, so other avatars drawing the same hair are untouched.
trimHairwins overhideHairandno_hair, which a Portals item that trims also carries for runtimes that can only hide the hair whole. A costume that takes the head slot still hides the hair.setHairStylenow reapplies what the worn items do to hair, so a new style is hidden or trimmed under a hat straight away. It used to show through until the next equip orrefreshVisibility().beanie.glbjoins the reference templates: a snug knit beanie fitted over the male and female Portals avatar's scalp, for trying hair trimming.
0.54.0 — 2026-09-29
Wearables can be cloth: a sheet, robe or dress that hangs on the body, swings, and is pushed by the limbs inside it.
PORTALS_wearable.clothnames a proxy mesh the SDK simulates, the skinned meshes it drives, and the proxy vertices pinned to the body, with its thickness, bending, damping, friction, gravity, inertia and drag.- The proxy runs as position-based cloth against the wearer's canonical body colliders and the floor. It is tethered to its pins by the length of cloth between them, so it never stretches under its own weight. It travels with the wearer, by the hips' place and heading, as far as its inertia lets it; its damping acts only on its motion relative to the wearer, and the air holds it back face-on as far as its drag says, so it billows and trails without being dragged thin. Limbs collide one-sided, so a fast hand pushes the cloth out instead of punching through it. Folds are kept from passing through each other, and
maxDistanceholds each part of the cloth within a distance of where the body would carry it, from none at the pins to all of it at the cloth's farthest, so sprinting and jumping never tangle it. While the wearer is in the air the floor stays where they last stood. - The cloth keeps the folds it rests in: it resists bending further than its proxy's rest shape, without resisting its threads' give, so it folds broadly rather than crumpling. Where the body would carry the cloth into itself, as in a lean or a tuck, the cloth is held outside it, and the fabric's length comes before its
maxDistance, so neither pulls it thin. Where it would carry the cloth below the floor, as a landing squat does, the cloth pools out along the floor instead of piling onto itself, and folds pushed apart in a pile move by the average of their pushes, so a landing never leaves the cloth caught through itself. - The driven meshes keep their full detail: each vertex is bound to the point of the proxy it lies over along its normal, and follows that point's frame as the cloth bends. A driven mesh can be thick cloth: a closed shell with a face either side of the proxy, whose two faces hang from the same points.
- Cloth starts from its rest shape, eased into the body's pose, when it goes on, when it starts running again, and after a teleport.
clothBudget(default 2048 particles) shares cloth across a crowd the waydynamicsBudgetshares springs: the controlled avatars first, then by distance. Past it, below full quality or with reduced motion, the driven meshes show their own skinning, springs and all.getDynamicsStats()reportsclothParticles,clothBudgetandclothRunning, andWearableManager.clothParticleCount()andisClothRunning()report each avatar's.- A wearable may carry two pieces of cloth and 2,048 particles (
WEARABLE_LIMITS.cloths,WEARABLE_LIMITS.clothParticles). The static file leaves the proxy out. - Springs no longer drop a clip's bones to rest while the clip holds still. Each frame they undo only their own swing, where they used to reset every joint to rest, so a clip holding a pose between two equal keyframes stays in it.
- Double-sided wearable meshes cast their shadows from just behind themselves, so a lit scene no longer draws stripes across them (shadow acne).
- Covers now also hide the clothing worn under an item, the skinned items that cover nothing themselves. Rigid accessories and other covering items stay.
ghost-sheet.glbruns as cloth: its drape, 1,584 particles pinned at the head, drives a sheet that is that cloth's own surface subdivided, in one colour the light shades. The sheet is heavy wool, 8 mm thick, with a rolled hem and eye holes; it holds broad folds and settles quickly instead of fluttering. It is draped in wool over the head, neck, torso and hips, which hold it; the arms and legs hidden under it pass through. It keeps within 0.3 m of its drape and slides over the floor. It hangs on its springs where cloth can't run.
0.53.0 — 2026-09-29
Wearables can hide the parts of the body they cover, so nothing of the wearer shows through a costume, a robe or a pair of gloves.
PORTALS_wearable.coverslists the regions of the wearer's body an item hides, from the standard's sixteen: the head, neck, torso and hips, and each upper arm, forearm, hand, thigh, shin and foot. The regions areWEARABLE_BODY_REGIONS, andWEARABLE_BODY_REGION_ROOTSandbodyRegionOfBone()say which rig bones belong to each.- A covered region is left out of the wearer's body, face, hair and base outfit, down to the triangle. Each triangle belongs to the region its vertices are most weighted to. Only the index of the mesh this avatar draws changes, so other avatars are untouched and nothing is copied but the index.
- Covers from every worn item add up. Taking an item off shows again only what no other item still covers.
WearableManager.getCoveredRegions()reports what is hidden, andEquippedWearable.coverswhat each item covers.ghost-sheet.glbcovers every region, so nothing of the body shows as the sheet swings.
0.52.0 — 2026-09-29
Springs honour VRMC_springBone's center, so clothing moves with the body instead of streaming behind it.
- A spring with a
centernode simulates in that node's space: moving the node, or anything above it, carries the chain along and adds no inertia, while gravity, wind and stiffness still act in the world. Centre a sheet, dress or skirt on the hips and it swings from the body's own motion and the limbs it drapes over, not from how fast the player runs. A spring without one still streams behind a runner, as a cape should. - The centre may be a Portals rig bone, resolved on the wearer's body, or one of the wearable's own nodes. A centre an avatar can't find leaves that chain in world space. A teleport leaves centred chains where they hang.
SpringCenterandSpring.centerdescribe it for hosts that read springs themselves.- The reference templates gain
ghost-sheet.glb: a bedsheet ghost draped over a standing Portals avatar, with eye holes cut through it. Its cloth over each arm follows the arm. The rest hangs on twelve spring chains centred on the hips, which billow while it stands and flare while it walks and runs.
0.51.0 — 2026-09-28
Wearables' effects, with no game code: particle emitters and ribbon trails from the file's PORTALS_wearable descriptor.
- Emitters draw one point cloud each, sized in metres, in the wearable's space (
local) or the world's (world), with the file's shape, rate, bursts, lifetime, speed, size and colour over life, gravity, sprite or flipbook, and blending. - Trails draw a ribbon along where their node has been, fading by age.
- Both follow the same triggers as clips; an
equipeffect runs for the first second the item is on. - They run last in the dynamics pass, after springs, so they leave from where the node really is, and follow the avatar's dynamics quality:
reducedhalves every emitter's rate and cap,offand reduced motion stop them. - Nodes that only carry an effect (a hat's tip, a sword's point) now travel with the wearable on both attachment forms.
WearableManager.effectStats()andavatars.getDynamicsStats()report live particles and trail points;wearableEffectTexturesOf()hands a host that loads wearables itself the sprites its emitters use.
0.50.0 — 2026-09-28
Wearables' secondary motion, with no game code: capes, skirts, hair and tails swing on VRMC_springBone 1.0 chains of their own bones.
- Springs run last in
avatars.update(dt), after everyonAfterUpdatebone writer, so they follow ragdolls and aim poses. An avatar you update yourself runs them inupdate(dt), or callavatar.updateDynamics(dt)after your own bone writes. - Collider groups named
portals:*use Portals' canonical body shapes for the wearer's body type, fitted to the Portals avatar bodies, so a cape collides with every body the same way. avatars.setWind()sets a wind every spring feels.- A body that jumps more than 3 m in a frame carries its springs along instead of stretching them;
avatar.resetDynamics()re-seats them at rest, andcontroller.respawn()does it for you. - A crowd budget (
dynamicsBudget, default 512 joints) runs the avatars the game controls first, then the rest by distance; past it springs run without collision, then hold their pose.getDynamicsStats()reports what runs. reducedMotion/setReducedMotion()holds every wearable still, for players who ask for it.wearableSpringBonesOf()andparseSpringBones()read and validate a file's chains.
0.49.0 — 2026-09-28
Wearables that follow the Portals wearable standard now move as their files describe, with no game code:
- A skinned wearable's extra bones, such as a cape chain or a tail, stay its own instead of collapsing onto the hips. Each chain hangs from the rig bone above it, so clips move it and two wearables can both have a bone named
Cape_01. - Clips bound to triggers play on cue:
always,equip(once, when the item goes on), the locomotion states andemote. The highest priority matching clip plays and cross-fades by itsfade; at equal priority the more specific trigger wins. - The SDK reads the avatar's state from the body clip it plays. A game that animates the body itself reports it with
avatar.setMotionState(state, { emote }); another wearable host usesWearableManager.setMotion(). - Allowlisted material animation (
KHR_animation_pointer: base colour, emissive colour and strength, texture scroll) plays on each wearer's own copy of the material, so one avatar's glow never pulses on everyone wearing the item. wearableDescriptorOf(),parsePortalsWearable()andregisterWearableLoaderPlugins()expose the standard for hosts that load wearables themselves.- Items without a
PORTALS_wearabledescriptor behave exactly as before: the legacyanimatedflag loops the first clip. createAvatarFromPlayer()loads a wearable's moving version (dynamicGlb) when the saved look carries one; older SDKs keep loading its still version (glb).
0.48.0 — 2026-09-28
WearableManageraccepts any body that implementsWearableHost, not only aGuardianAvatar, so a renderer with its own Portals avatar body dresses it with the same wearable code games use.GuardianAvatarimplements it; nothing changes for existing games.equipLoaded()equips a wearable whose GLB you already loaded, synchronously, andreplaceAllLoaded()swaps a whole outfit in one step so a changed look never shows a half-dressed avatar.refreshVisibility()re-applies every equipped item's hides after a host changes body visibility itself, such as re-showing a hair style.unequipEntry()takes off exactly one equipped entry, andisAnimating()tells an on-demand renderer whether any equipped item moves on its own.- Taking off shoes whose missing half the SDK rebuilt now frees the rebuilt geometry instead of keeping it on the GPU.
0.47.0 — 2026-09-21
- Animated wearables play again. Every animated item in the Portals catalog is a back wearable — the fish-tank backpacks, the toucan and shoulder parrot, the cobra, the tiny rockling, the rocket pack — and the SDK now runs the animation embedded in the item's GLB, on loop, for as long as it is worn. An item from a player's inventory needs nothing from the game; a wearable a game ships itself sets
animated: true, and can passanimationsalongsideobject. The mixers run offavatars.update(dt)and stop when the item comes off,releaseToWorldincluded. - Those same items used to disappear entirely when equipped. They carry a small self-rigged prop — a fish on a bone called
Bone— and any skinned mesh was treated as Portals avatar clothing, so the prop was rebound to the avatar's hips and the item's own armature was stripped, taking the rest of the wearable with it. A skinned mesh now counts as clothing only when its skeleton really shares bones with the Portals avatar; anything else attaches as the rigid item it is. - A wearable's animation is scoped to the part it belongs to, so a clip written against a generic bone name cannot move the Portals avatar or another equipped item.
- Hair is hidden under a costume that encloses the head — one that occupies
full_bodyandhat, as the chicken, spider and dino suits do — not only on theno_hairflag, which no catalog costume sets. A one-piece garment that leaves the head out, like dungarees, keeps the hair and the shoes. This matches the rule the website renderer uses. - Skinned wearable bone matching tolerates unsanitized
mixamorig:names, as rigid attachment already did.
0.46.13 — 2026-09-18
- First-person camera faced the wrong way: the eye was placed half a turn from the heading the movement code uses, so W walked backwards and A/D were swapped. The view now matches
getFlatForwardin both camera modes. Games that flipped the camera themselves after the SDK placed it should drop that workaround when pinning this version.
0.46.12 — 2026-09-04
- Finishing a ledge climb no longer snaps the body back to where the jump started when the final stand-point check fails. The character lands at the target, is pushed out of anything it grazes, and the normal collision loop takes over. Fixes the "teleported back to the jump" reports.
0.46.11 — 2026-09-03
- Ledge probing runs every Nth airborne frame instead of every frame, with the first airborne frame always probing, so a jump at a ledge still catches it. New controller option;
1restores per-frame probing. - Falling grabs only take ledges within 80° of the heading (was 150°), so the body no longer spins around after a grab. Rising grabs keep 55°.
- The stand point on top of the ledge is found before the climb plays, not after. Irregular tops aim the climb at a nearby spot that fits and refuse the grab when none does.
- The camera eases in behind the body over the climb instead of snapping, and only for moderate turns.
0.46.0 — 2026-08-29
- Ledge climbs are gated on a capsule sweep of the path the root will travel: up the wall and over the lip. New optional collision hook
sweepCapsule(from, to, radius, height);SceneCollidersupplies one, and without it the controller stepsdepenetratealong the path. A blocked path refuses the grab. - After landing, the stand point is verified again. A body that is not free there moves to the nearest standable spot on the ledge, or falls back from where it grabbed.
0.45.0 — 2026-08-29
- Touch controls gain a fullscreen toggle next to the camera toggle wherever the browser offers the Fullscreen API. iPhone Safari has no element fullscreen, so the button is absent there.
0.44.0 — 2026-08-28
- Ledge climb safety: the climb drives the root without collision, so the landing is now checked when the body is handed back. Depenetration fixes a landing that clips the ledge's back wall; a landing still inside the level is undone immediately by the stuck rescue instead of a second later.
- For one second after a climb the buried watchdog treats a walkable face overhead as proof of burial on its own.
- The buried check no longer misreads a swimmer, or a body under a single-sided slope with its head above the surface, as buried.
0.43.0 — 2026-08-26
- Rigid wearable attachments fixed. A mesh-bearing node authored under a bone such as
mixamorig:Headis followed as one logical part, even when the loader expands it into a multi-primitive group, and its authored local transform is preserved without attaching the wearable's duplicate armature.attachBoneandoffsetstill override it. - A flat GLB with no authored bone uses the natural bone for its declared slot. Body-sized or slotless items keep avatar-root coordinates instead of defaulting to the head.
0.42.0 — 2026-08-26
avatars.onAfterUpdate(cb)runs a callback after the animation mixer tick, so procedural posing, ragdoll blending and tooling can write bones without being overwritten. Returns an unsubscribe function.- The Pose Tuner dev tool ships alongside this release and depends on it.
0.41.0 — 2026-08-25
- No SDK code change. The hosted
three/addons/mapping gains the postprocessing set:EffectComposer,RenderPass,ShaderPass,MaskPass,Pass,UnrealBloomPass, plus theCopyShaderandLuminosityHighPassShadershaders. Games reaching for a bloom effect no longer 404 on publish.
0.40.0 — 2026-08-24
- Collision fixes for the character controller and NPCs.
- NPCs gain a
stepDownoption and a buried-floor recovery: an NPC dropped through a floor is put back on the floor it was standing on. The recovery uses theisUnderSurfaceprobe thatcollider.controllerOptions()already hands over. GroundProbetype exported fromSceneCollider.
0.39.0 — 2026-08-21
- Merge release. The first version that contains both branches of 0.36.0 and 0.37.0 below, together with 0.38.0. No other changes.
0.38.0 — 2026-08-18
HealthController: hit points, flinch reactions, a death take and respawn for one Portals avatar. Reactions play on the base animation layer so they travel withanimations.getCurrent()in multiplayer, and the body is locked for exactly as long as a reaction plays. Callwarm()when combat becomes likely.MeleeSweep: continuous melee hit detection. The weapon is a capsule measured off the equipped GLB, the target is capsules over the posed skeleton, and the blade path is swept every frame of the strike so fast swings cannot tunnel.readRootYaw()andwriteRootYaw()on the character controller.- Animation sync fix.
0.37.0 — 2026-08-17 and 2026-08-19
Two parallel branches. The first version with both is 0.39.0.
- Stuck rescue (2026-08-17). New collision hook
isUnderSurface(position, maxDistance?)and controller optionstuckRescue(defaulttrue): a character falling for over about 0.7 s with a walkable surface overhead is put back at its last grounded position. Landing also uses a per-frame continuous ray along the feet's true displacement, so a fast or diagonal fall cannot cross a floor without landing on it.depenetratepushes a body whose capsule centre crossed a floor straight back up. - Shouldered scope and shooting from the barrel (2026-08-19).
ScopeTuningfor the shouldered aim stance, andMuzzleWorldOptionsplusShotOriginParamsto read the shot origin from the weapon's barrel.
0.36.0 — 2026-08-17
Two parallel branches. The first version with both is 0.39.0.
- Ragdoll, weapon posing and shooter strafing.
RagdollOptionsandRagdollTuning;aimWeaponwith three stances (GunPoseName,LoadGunPosesOptions,AimBendTuning,CarryTuning,RecoilTuning,ShotKick); arm IK; shooter-grade strafing throughstrafeTuning(StrafeTuning,StrafeCollapse);WearableOffset. - Swimming collision fix and collision optimisation. New optional collision hook
depenetrateSegment.
0.35.0 — 2026-08-13
- Swimming collision fix. New optional collision hook
resolveMovement({ current, proposed, radius, airborne }).
0.34.0 — 2026-08-12
- Ground probing is capsule-shaped:
getGroundHeight(x, z, y?, radius?)receives the capsule radius andSceneColliderprobes the centre plus four rim rays, so a landing half on a ledge stands on it instead of falling through. - Avatar-vs-avatar collision. Controllers and NPCs created through
PortalsAvatarspush out of every other avatar's capsule automatically (XZ only, hidden avatars skipped). Opt out per character withcollideWithAvatars: false; hand-built setups wire the obstacle set withsetAvatarObstacles(). NPCs are solid by default. - Camera rig adjustments.
- The README describes these features as 0.32.0. That number was never released; the bump went from 0.30.0 straight to 0.34.0.
0.30.0 — 2026-08-11
- Camera rig fix.
ControllerOptions.cameraCollisionandcontroller.cameraRig.collision.enabledcontrol obstacle avoidance, on by default wherever araycasthook is wired. The boom shortens on contact and drops to first person when squeezed.
0.29.0 — 2026-08-11
- The
depenetrate(position, radius, height)hook is documented and run after the vertical step each frame: walls push horizontally, ceilings push down, floors stay the job ofgetGroundHeight. - Animation controller fix for camera avoidance.
0.28.0 — 2026-08-10
- Capsule collider. New optional collision hook
depenetratefor capsule overlap recovery, which the swept probes cannot do on their own;GuardianAvatarexposes its capsule dimensions.
0.27.0 — 2026-08-10
- Merge release. The first version that contains both 0.23.0 and 0.26.0. No other changes.
0.26.0 — 2026-08-10
- Camera obstacle avoidance (
CameraCollisionOptions) and camera rotation limits (CameraLimitOptions). - Swimming.
- Falling, stairs and step-downs.
- Touch control and catalog adjustments.
0.23.0 — 2026-08-09
- Current Portals player.
avatars.createAvatarFromPlayer(player)builds the avatar a signed-in player saved on/avatar;nullmeans guest. TypesPortalsPlayerProfile,PortalsPlayerAvatar,PortalsPlayerFullAvatar,PortalsPlayerAvatarWearableandPlayerAvatarLoadOptions.
0.22.0 — 2026-08-07
- Touch controls for mobile:
TouchControlswith a virtual joystick and action buttons, wired into the character controller.
0.21.0 — 2026-08-07
- NPCs:
NPCControllerfor code-driven avatars. - Loading performance: asset fetching moves to a worker.
- Eye material and wearable loading adjustments.
0.20.0 — 2026-08-05
- Run styles.
RunStyleandHiddenRunStyleswap the jog band of the locomotion set. - Catalog expansion.
0.13.1 — 2026-08-04
- Melee: a single click strikes with alternating side slashes; holding the press for
controller.meleeHoldTimeseconds (0.3) launches the authored combo.useItemCombo()triggers the combo directly. - Remaining references to the Invector controller the port was based on were removed.
0.10.0 — 2026-08-04
- New animation clip set and the
ANIMATION_SETSopt-in behaviour groups. - Throwing an item away and picking it up again (
ReleasedWearable). - Strafing (
StrafeMode). - Movement speed and stride controls.
0.9.0 — 2026-08-03
- Overlay clips accept
sync: trueto take their phase from the base locomotion clip, so a carry clip with an arm swing stays in step with the run. - Throwing tested and fixed.
0.8.0 — 2026-08-01
- Avatar switching fix:
adoptFromcarries controller state over when the avatar is swapped. boneAdjust: corrective Euler rotations applied to named bones' keyframes when a clip is registered.- Hand-item overlays are declared up front through
DEFAULT_HAND_ANIMATIONS, so the carry pose is found the moment something lands in the right hand.
0.5.0 — 2026-08-01
portalsItemIdon wearable definitions: equip a catalog item by its inventory id. Hand grab.
0.4.1 — 2026-08-01
- Ledge grab:
probeLedge,LedgeGrab,LedgeProbeParams. SceneColliderreplaces the earlier box collider world.getGroundHeightcollision hook.
0.2.0 — 2026-08-01
- Avatar setup pipeline,
preloadAnimations, face animation controller, andTHREEre-exported from the SDK so a game has one Three.js instance.
0.1.0 — 2026-07-31
- Initial release: body types, skin, hair and eye colours, hair styles, the wearables system with the never-naked basic set, retargeted locomotion clips, hand items, facial animation, and the first and third-person character controller with camera rig.
