npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@blcklab/anyo-player

v0.5.7

Published

Production-ready browser player for Anyo worlds with Sekai64 rendering, lifecycle, input, recovery, navigation, XR, and framework adapters.

Readme

@blcklab/anyo-player

A browser player for Anyo worlds.

@blcklab/anyo-player takes a world document, gives it a Sekai64 renderer, and owns the browser-facing work around it: loading, input, lifecycle, recovery, responsive sizing, accessibility, world replacement, and optional framework adapters.

It stays deliberately small at the package boundary. Player does not bundle Anyo, Sekai64, animation, avatar, physics, audio, or framework runtimes for you; those remain peer or host-installed packages so an application can choose exactly what it needs.

Install

npm install @blcklab/anyo-player @blcklab/anyo @blcklab/sekai64

Player 0.5.0 is validated with:

@blcklab/anyo      0.10.0-rc.1
@blcklab/sekai64   0.8.0-rc.33

Start in a minute

import { createAnyoPlayer } from '@blcklab/anyo-player'
import '@blcklab/anyo-player/styles.css'

const container = document.querySelector<HTMLElement>('#world')!

const player = createAnyoPlayer({
  container,
  source: '/world.anyo.json',
  renderer: {
    backend: 'auto',
    antialias: true,
  },
  exploration: {
    desktop: true,
    touch: true,
    pointerLock: true,
  },
})

await player.load()

When the page, route, or component that owns the player is removed, dispose it too:

await player.disposeAsync()

That final disposeAsync() matters in production. It releases browser listeners, input ownership, renderer resources, object URLs, and the active Anyo runtime owned by that Player instance.

Borderless canvas focus

The 3D canvas stays visually borderless when it receives keyboard/programmatic focus, including fullscreen. Player buttons and other interactive controls still keep their normal focus indicators.

Hosts that explicitly want a canvas focus ring can opt in with CSS variables:

.anyo-player {
  --anyo-player-canvas-focus-outline: 2px solid var(--anyo-player-ui-accent);
  --anyo-player-canvas-focus-outline-offset: -2px;
}

What can be loaded?

World 0.9 modular documents are supported when the source has a URL context. For JSON/blob sources, provide baseUrl; Player resolves root imports[*].src against that document URL before Anyo core resolves native Object 0.1 imports. Direct in-memory documents with relative imports still require the caller to provide source context through a supported source form.

Player accepts a URL, JSON text, a world document object, local browser files, virtual folder maps, and trusted host-decoded archives.

await player.load('/worlds/store/world.anyo.json')
await player.load({
  files: {
    'world.anyo.json': worldJson,
    'assets/store.glb': storeModel,
  },
})

For <input webkitdirectory multiple>, use createAnyoPlayerFolderSource() to preserve relative package paths safely.

Replace a world without replacing the player

Once a Player is ready, use replaceWorld() instead of calling load() again:

await player.replaceWorld('/worlds/gallery/world.anyo.json')

For applications with several known worlds, Player also provides registration, preloading, cancellation, and navigation APIs.

const unregister = player.registerWorld('gallery', '/worlds/gallery/world.anyo.json')

await player.preloadWorld('gallery')
await player.navigateTo('gallery')

unregister()

Host actions stay in the host

World JSON describes the world. Executable application behavior stays in trusted application code.

player.registerAction('open-product', ({ sku }) => {
  console.log('Open product', sku)
})

The same rule applies to plugins, custom asset loaders, Web Surface applications, archive decoders, analytics sinks, and other executable integrations.

Animated GLB and VRM

Animation and avatar support are optional host integrations. They are not hard dependencies of Player.

The companion versions validated with Player 0.5.0 are:

npm install @blcklab/[email protected] @blcklab/[email protected]

For animated GLB and VRM, create one shared Sekai64 animation integration and give its animation module to the VRM loader:

import { createAnyoPlayer } from '@blcklab/anyo-player'
import { createSekai64AnimationIntegration } from '@blcklab/anyo-animation/sekai64'
import { createSekai64VrmAssetLoader } from '@blcklab/anyo-avatar/vrm/sekai64'

const animation = createSekai64AnimationIntegration()

const player = createAnyoPlayer({
  container,
  source: '/world.anyo.json',
  renderer: {
    modules: [animation.module],
    assetLoaders: [
      animation.assetLoader,
      createSekai64VrmAssetLoader({
        animationModule: animation.module,
      }),
    ],
  },
  plugins: [animation.plugin],
})

await player.load()

The shared module is intentional. A VRM is a skinned glTF asset; the VRM loader and anyo.animation must work against the same mixer and skeleton runtime.

Create fresh integration and loader registrations for each Player lifecycle. The renderer owns the lifetime of registered loaders.

Web Surfaces

Player enables Anyo Web Surface presentation by default. World data remains declarative; trusted application code remains in the host.

const player = createAnyoPlayer({
  container,
  source: '/world.anyo.json',
  webSurface: {
    apps: {
      dashboard: {
        mount(container, props, context) {
          const button = document.createElement('button')
          button.textContent = String(props.label ?? 'Open')
          button.addEventListener('click', () => {
            void context.runAction('open-dashboard')
          })
          container.append(button)

          return {
            update(nextProps) {
              button.textContent = String(nextProps.label ?? 'Open')
            },
            dispose() {
              button.remove()
            },
          }
        },
      },
    },
    externalUrls: {
      allowedOrigins: ['https://docs.example.com'],
      sandbox: ['allow-forms', 'allow-popups'],
    },
  },
})

Player does not accept executable HTML or JavaScript from world JSON and does not bypass iframe CSP, X-Frame-Options, or browser cross-origin restrictions.

Set webSurface: false when another trusted plugin owns that presentation path.

Optional plugins and systems

Trusted host-created Anyo plugins can be appended through plugins. Fixed-step systems can be supplied through systems and systemOptions.

const player = createAnyoPlayer({
  container,
  source: '/world.anyo.json',
  plugins: [myPlugin],
  systems: [mySystem],
})

This is how packages such as physics, audio, VFX, map integrations, or custom application features can participate without becoming Player dependencies.

Custom element

import { defineAnyoPlayerElement } from '@blcklab/anyo-player/element'

defineAnyoPlayerElement()
<anyo-player
  src="/world.anyo.json"
  activation="visible"
  preload="source"
  backend="auto"
  pause-when-offscreen
  aria-label="Virtual world"
></anyo-player>

The custom-element subpath is side-effect free. Registration happens only when defineAnyoPlayerElement() is called. If you explicitly want automatic registration, use @blcklab/anyo-player/element/define.

Vue and React

Framework adapters live on separate subpaths so the core package never requires a framework at runtime.

import { AnyoPlayer } from '@blcklab/anyo-player/vue'
import { AnyoPlayer } from '@blcklab/anyo-player/react'

Vue, React, and React DOM are optional peer dependencies. Install only the framework used by your application.

Runtime controls and diagnostics

Player exposes production controls without making the world document authoritative over the host device.

player.setQualityPreset('high')
player.setTargetFps(60)
player.setDynamicResolution(true)
player.setViewPreference({ fieldOfView: 72, pointerLookScale: 1.1 })
player.showCaption('Welcome to the showroom.', { speaker: 'Assistant' })

const health = player.createRuntimeHealth()
const report = player.createRuntimeReport()
const bundle = player.downloadDiagnosticBundle()

Camera and inspection helpers include:

player.setCameraMode('orbit', { bounds })
player.setCameraMode('top', { bounds, northUp: true })
player.setCameraMode('free', { speed: 12 })
player.setCameraMode('explore')
player.teleport({ position: [0, 1.65, 0], resetMotion: true })

Production notes

Before shipping a Player host:

  • serve worlds and assets with correct CORS headers when they are cross-origin;
  • call disposeAsync() on route or application teardown;
  • keep executable plugins and loaders in trusted host code, never in world JSON;
  • treat pointer lock, fullscreen, audio unlock, and XR as user-gesture-gated browser capabilities;
  • use a secure context for WebXR;
  • configure a fallback renderer when your application needs recovery from WebGPU loss;
  • validate real devices and browsers that matter to your users.

The full deployment and lifecycle guide is in docs/PRODUCTION.md.

Documentation

Development

npm install
npm run build
npm test
npm run check

Generated output is written to dist/ and is intentionally not committed.

Maintainer-only release notes, evidence procedures, historical milestone documents, and internal architecture notes live under .internal/ in the source repository. That directory is intentionally excluded from the npm package.

License

MIT License. Copyright (c) 2026 Avelurs Tinio.

Third-person foundation

PlayerBody owns movement; the camera is a derived view. To control an ordinary entity:

await player.setCharacterAnchor({ character: 'avatar', facing: 'movement', yawOffset: Math.PI })
player.setThirdPersonCamera({ distance: 2.4, targetHeight: 1.05, collision: true })
console.log(player.viewState) // eye, feet, camera, target, yaw, pitch, facing, distances

Yaw offset depends on the model's authored forward direction. Avatar fitting belongs to the host. For skinned VRM/GLB, use the current validated Sekai64 0.8.0-rc.34 line.

Set setThirdPersonCamera(false) to restore the body eye view. Configure or release the character anchor separately if you want the avatar out of the first-person view. Camera collision is conservative AABB-based. This development revision does not select locomotion animation clips.

MMORPG mouse orbit (0.5.1)

Enable drag orbit explicitly so existing pointer-lock integrations remain backward compatible:

player.setThirdPersonCamera({
  distance: 4,
  targetHeight: 1.35,
  collision: true,
  orbit: {
    button: 2,          // right mouse
    minDistance: 1.5,
    maxDistance: 10,
    minPitch: -1.15,
    maxPitch: 1.1,
  },
})

Hold the configured mouse button and drag to orbit around the Player body. Use the mouse wheel to zoom. WASD stays relative to the orbit camera heading, while facing: 'movement' characters remain visually independent from the camera and facing: 'camera' characters continue to support directional locomotion sets.

Desktop orbit input is bound directly to Player's camera controller so Player-specific RMB-drag and wheel-zoom hooks remain reachable even though Anyo's world.exploration facade intentionally exposes only the stable base movement/look contract.

Camera smoothing and obstruction recovery (0.5.2)

Third-person smoothing is enabled by default. Player keeps authoritative body movement immediate while the visual follow target and zoom arm use frame-rate-independent damping. Camera collision remains conservative: an obstruction shortens the arm immediately, while the camera eases back outward after the obstruction clears.

player.setThirdPersonCamera({
  distance: 4,
  targetHeight: 1.35,
  collision: true,
  smoothing: {
    horizontalTargetResponse: 18,
    verticalTargetResponse: 10,
    zoomResponse: 16,
    collisionRecoveryResponse: 8,
  },
  orbit: true,
})

Most hosts should use the defaults. Set smoothing: false when reproducing the pre-0.5.2 immediate camera response for compatibility testing.

LMB free-look + RMB authoritative orbit (0.5.3)

When third-person orbit is enabled, the default desktop MMORPG controls are now:

  • LMB drag — free-look around the character. Camera yaw/pitch changes, but PlayerBody movement yaw and character facing remain unchanged.
  • RMB drag — authoritative orbit. RMB adopts the current camera heading as the movement-facing heading, then camera-relative WASD and character-facing yaw continue to follow RMB orbit.
  • Mouse wheel — zoom, using the 0.5.2 smoothed camera arm.
player.setThirdPersonCamera({
  distance: 4,
  collision: true,
  orbit: {
    button: 2,           // RMB: authoritative orbit
    freeLookButton: 0,   // LMB: visual free-look (default)
  },
})

Set freeLookButton: false to disable free-look. Existing configurations that use button: 0 for authoritative LMB orbit remain compatible; in that case free-look defaults off unless another button is explicitly selected.

Camera polish controls (0.5.4 candidate)

The optional P2/P3 camera layer builds on the 0.5.2 smoothing and 0.5.3 MMORPG mouse ownership without changing PlayerBody locomotion ownership.

player.setThirdPersonCamera({
  distance: 4,
  collision: true,
  shoulderSide: 'right',
  shoulderOffset: 0.45,
  characterVisibility: {
    hiddenDistance: 0.5,
    fadeStartDistance: 1.35,
  },
  dynamicFieldOfView: {
    maxBoost: 5,
    response: 7,
  },
  orbit: {
    button: 2,
    freeLookButton: 0,
    sensitivityX: 1,
    sensitivityY: 0.8,
    invertX: false,
    invertY: false,
  },
})

Runtime shoulder switching keeps the configured magnitude and uses the normal third-person smoothing path:

player.swapThirdPersonShoulder()
player.setThirdPersonShoulder('left')
player.setThirdPersonShoulder('right', 0.55)
player.setThirdPersonShoulder('center')

viewState.characterVisibility is a renderer-neutral 0..1 signal derived from the actual camera distance after collision. Player does not modify VRM/GLB materials; hosts may use this value to fade or hide the local character without coupling Player to Avatar or Sekai64 material internals.

Dynamic FOV is disabled unless dynamicFieldOfView is explicitly enabled. By default, normal walking keeps the base FOV unchanged and widening starts above the configured walk speed, reaching maxBoost at run speed. Disabling third person or leaving explore mode restores the base perspective FOV.

For compatibility, the legacy orbit.sensitivity still drives both axes unless sensitivityX or sensitivityY is provided.

Locomotion telemetry

player.locomotion is a read-only snapshot for animation and host integrations. It reports collision-resolved horizontal speed/velocity, vertical velocity, grounded state, run intent, configured walk/run speeds, and a reset serial. Consumers should drive animation from horizontalSpeed, not raw keyboard intent, so collisions and blocked movement remain visually correct.