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

@ubox-lib/ubox-machine

v1.0.1

Published

State machine and Kinect sensor integration for Ubox interactive experiences

Readme

CDN

<script src="https://unpkg.com/@ubox-lib/ubox-machine/ubox-machine.min.js"></script>

The library exposes a global Ubox object with two factory methods and an event system:

| | Description | | ------------------------- | ------------------------------------------------------- | | Ubox.setMachine(config) | Creates and returns an AppMachine instance | | Ubox.setSensor(config) | Creates a PhygitalMove sensor linked to a machine | | Ubox.Event | Event aggregator for state entry/exit and custom events |


AppMachine

A state machine for managing application flow and user interactions.

Quick Start

const machine = Ubox.setMachine({
  initialState: "idle",
  paths: {
    idle: { trigger1: "stateA", trigger2: "stateB" },
    stateA: { next: "stateB", back: "idle" },
    stateB: { complete: "idle" },
  },
  userCallback: () => {},
});

// Define state behaviors
Ubox.Event.registerEntry("idle", () => {
  document.querySelector("#idleScreen").style.display = "block";
});

Ubox.Event.registerExit("idle", () => {
  document.querySelector("#idleScreen").style.display = "none";
});

// Start the machine — triggers the initial state's entry
machine.init();

State Management

Defining State Paths

paths: {
  stateA: {
    action1: "stateB",    // When 'action1' is called in 'stateA', transition to 'stateB'
    action2: "stateC"     // When 'action2' is called in 'stateA', transition to 'stateC'
  }
};

State Transitions

// Triggers a transition based on the current state and the given action.
// Invokes the appropriate exit and entry methods/events.
machine.newState("action1");

// Manually sets the machine to a specific state.
// Does NOT trigger any entry or exit methods/events.
machine.state = "stateA";

The "any" State

The any state is a global fallback: triggers defined in any apply to all states, but a trigger defined in a specific state always takes priority over any.

For example, consider the following state machine:

machine_A.png

You could build this with the Ubox library as follows:

const machine = Ubox.setMachine({
  initialState: "stateA",
  paths: {
    stateA: { trigger1: "stateB", trigger2: "stateC" },
    stateB: { trigger1: "stateA", trigger2: "stateC" },
    stateC: { trigger1: "stateA", trigger2: "stateB" },
  },
});

This works, of course. However, as the number of states grows, this can quickly become a "nest" of redundant connections. We can simplify this using the any state:

machine_B.png

Now, all three states (A, B, and C) can access each other through the any path. Here is how you implement this in Ubox:

const machine = Ubox.setMachine({
  initialState: "stateA",
  paths: {
    stateA: {},
    stateB: {},
    stateC: {},
    any: {
      trigger1: "stateA",
      trigger2: "stateB",
      trigger3: "stateC",
    },
  },
});

If a trigger is defined in both a specific state and the any path, the specific state takes priority. The any path acts as a fallback only when the trigger is not found in the current state.

const machine = Ubox.setMachine({
  initialState: "stateC",
  paths: {
    stateC: { trigger1: "stateB" }, // Priority
    any: {
      trigger1: "stateA", // Fallback
      trigger2: "stateC",
    },
  },
});

// If in 'stateC': trigger1 -> 'stateB' (local override)
// If in any other state: trigger1 -> 'stateA' (global fallback)

Entry and Exit Callbacks

Whenever a state transition occurs, the exit callback of the current state runs first, followed by the entry callback of the next state.

There are two equivalent ways to define these callbacks — use either or both:

Option 1 — inline methods in the paths config:

paths: {
  stateA: {
    action1: "stateB",
    action2: "stateC",

    entry: () => {
      console.log("Entered state A");
    },

    exit: () => {
      console.log("Leaving state A");
    }
  }
}

Option 2 — event listeners via Ubox.Event:

Ubox.Event.registerEntry("stateA", () => {
  console.log("Entered state A");
});

Ubox.Event.registerExit("stateA", () => {
  console.log("Leaving state A");
});

Both approaches are equivalent and can be combined freely on the same state.

Inspecting the Destination State from an Exit Callback

Before any exit callback fires, the machine sets machine.nextState to the state it is about to enter. You can read it inside an exit handler to conditionally react based on where the machine is going.

paths: {
  stateA: {
    action: "stateB",

    exit: () => {
      if (machine.nextState === "stateB") {
        console.log("Leaving stateA towards stateB — do something specific");
      }
    },
  },
}

This also works with Ubox.Event.registerExit:

Ubox.Event.registerExit("stateA", () => {
  console.log("Going to:", machine.nextState);
});

Initialization

Call machine.init() after all event listeners are registered to fire the initial state's entry callback.

const machine = Ubox.setMachine({
  /* configuration */
});

Ubox.Event.registerEntry("stateA", () => {
  /* ... */
});

machine.init(); // Triggers the entry for the initial state

Configuration

| Property | Type | Default | Description | | -------------- | -------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | initialState | String | undefined | The state the machine starts with. Can only be set in the constructor. | | paths | Object | {} | Defines all state transitions. Keys are state names; values are objects mapping trigger strings to target state names. Reserved keys per state: entry, exit, sub. | | maxUsers | Number | 1 | Maximum number of concurrent users. | | newUser | Boolean | true | Whether new users can be registered. | | userCallback | Function | f() | Called on every skeleton update with (skeleton, user). skeleton contains raw joint data; user is the corresponding object from machine.user. |

All properties are optional — defaults apply if omitted.

Runtime Properties

| Property | Type | Description | | -------------- | ------ | --------------------------------------------------------------------- | | user | Array | List of currently registered users. | | state | String | Current active state. | | prevState | String | Previous state (before the last transition). | | nextState | String | The state the machine is about to transition into. Set before exit callbacks fire; readable inside exit to know the transition destination. | | machinePaths | Object | The full state transition map (mirrors paths). | | subState | Object | The AppMachine sub-machine for the currently active state, or null. | | subStateName | String | Current active substate name. | | subStates | Object | All substate machines, keyed by parent state name. |

Methods

| Method | Parameters | Description | | -------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | init() | none | Fires the entry callback/event for initialState. Call once after all listeners are registered. | | newState | action - String | Transitions to the next state defined by the action. Fires exit of current state and entry of next. | | newSubState | action - String | Triggers a transition within the current state's active sub-machine. | | registerUser | jsonObject - Object | Registers a new user and dispatches a userRegistered event with the user as detail. Only registers up to maxUsers. | | removeUser | index - Number | Removes the user at index in machine.user and dispatches a userRemoved event with the removed user's data as detail. | | purge | none | Removes all users and dispatches a userPurge event. |

User Management

When using PhygitalMove, users are registered and removed automatically. User slots are 1-based (not 0-based): the first registered user has slot === 1.

The primary way to handle per-user data is through userCallback:

const machine = Ubox.setMachine({
  userCallback: function (skeleton, user) {
    console.log(`User in slot ${user.slot} updated`);
  },
});

registerUser, removeUser, and purge are managed automatically by PhygitalMove. When not using a sensor, you can call them manually or manipulate machine.user directly.

Manual registration example:

machine.registerUser({
  id: 1,
  type: "mobile", // useful for distinguishing input methods
  data: {}, // any additional data
});

User Events

// Triggered when a new user is registered
Ubox.Event.register("userRegistered", (user) => {
  console.log("New user on slot:", user.detail.slot);
});

// Triggered when a specific user is removed
Ubox.Event.register("userRemoved", (user) => {
  console.log(`The user on slot ${user.detail.slot} was removed`);
});

// Triggered when all users are removed (purged)
Ubox.Event.register("userPurge", () => {
  console.log("All users removed");
});

Substates

Substates add a nested layer of states that operate independently from the main state machine.

For example: a car has engineOn and engineOff as top-level states. When the engine is on, it also has gear states (neutral, drive, reverse). Instead of connecting every gear state back to engineOff, substates let you nest the gear logic inside engineOn.

states_A.png

Substates help solve this problem.

With substates, we can maintain a simple, direct connection between engine on and engine off, and then treat all the gear-related states as a separate diagram nested inside the engine on state:

states_B.png

Defining a Substate

Add a sub key to any state's config, with an initial property and substate definitions:

const machine = Ubox.setMachine({
  paths: {
    stateA: {
      action: "stateB",

      sub: {
        initial: "subA", // Active substate when stateA is entered

        subA: {
          action: "subB",
          entry: () => {},
          exit: () => {},
        },

        subB: {},
      },
    },

    stateB: {},
  },
});

Substate Transitions

// Trigger a transition in the currently active sub-machine
machine.newSubState("action");

// Access the currently active state's sub-machine directly
const subMachine = machine.subState;
subMachine.newState("action");

// Access a specific state's sub-machine (regardless of current state)
const subMachine = machine.subStates["stateA"];
subMachine.newState("action");

Substate Entry and Exit

Substates support the same entry/exit inline methods and Ubox.Event.registerEntry/registerExit listeners as top-level states.

Ubox.Event.registerEntry("subB", () => {
  console.log("Entered substate B");
});

Important: This constraint only applies when using registerEntry/registerExit. Because these listeners match by name globally, substate names must be unique across all parent states when using them. Inline entry/exit methods in the paths config are scoped to their own substate and have no such restriction.


Ubox.Event

Event aggregator for registering state entry/exit listeners and custom events.

Methods

| Method | Parameters | Description | | ---------------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------ | | register(eventName, method, priority) | eventName - String, method - Function, priority - String | Registers a listener for any custom event name. | | registerEntry(state, method, priority) | state - String, method - Function, priority - String | Shorthand for register("entry:state", ...). Fires when the named state is entered. | | registerExit(state, method, priority) | state - String, method - Function, priority - String | Shorthand for register("exit:state", ...). Fires when the named state is exited. | | remove(eventName, method) | eventName - String, method - Function | Unregisters a previously registered listener. |

Priority controls execution order and mode:

| Value | Behavior | | ----------------- | ----------------------------------------------------- | | "high" | Runs synchronously and before low-priority listeners. | | "low" (default) | Runs asynchronously. |

Example:

// High-priority entry listener (runs synchronously first)
Ubox.Event.registerEntry(
  "stateA",
  () => {
    console.log("Entered stateA");
  },
  "high",
);

// Unregister a listener
const handler = () => console.log("exiting stateA");
Ubox.Event.registerExit("stateA", handler);
Ubox.Event.remove("exit:stateA", handler);

PhygitalMove

Kinect sensor integration. Detects users in a physical zone and feeds their skeleton data into an AppMachine.

Configuration

const machine = Ubox.setMachine({
  userCallback: userFunction,
});

const sensor = Ubox.setSensor({
  draw: true, // Enable debug visualization
  calibration: true, // Enable interactive zone calibration
  machine: machine, // AppMachine instance (required)
  zone: [
    [0, 0], // Top-left X and Z
    [640, 4], // Bottom-right X and Z
  ],
});

Properties

| Property | Type | Default | Description | | ------------------- | ------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------ | | machine | Object | undefined | AppMachine instance to register detected users into. | | zone | Array | [[0, 0], [640, 4]] | Interactive area boundaries: [[topLeftX, topLeftZ], [bottomRightX, bottomRightZ]]. | | calibration | Boolean | false | Enables interactive zone calibration UI. | | draw | Boolean | false | Enables debug skeleton visualization overlay. | | ctx | CanvasContext | undefined | Canvas context for visualization (auto-created when draw is true). | | color | Array | ["#c92424", "#24c956", "#247fc9", "#4824c9"] | Skeleton colors per user slot when draw is active. | | mapZone | Boolean | false | Enables grid-based quadrant detection of the zone. | | mapGrid | Array | [1, 1] | Grid dimensions [rows, columns] when mapZone is true. | | kickUser | Boolean | true | Auto-removes inactive users who don't meet minMovement within inactivityTimeout. | | inactivityTimeout | Number | 15000 | Time (ms) before an inactive user is removed. | | minMovement | Number | 1 | Minimum pixel movement required to reset the inactivity timer. |

Methods

| Method | Parameters | Description | | ----------- | ---------------- | -------------------------------------------------------------------------------------------------------------------- | | normalize | joint - Object | Maps raw joint coordinates to [0, 1] relative to sensor resolution. Returns { x: Number, y: Number, z: Number }. |

Utilizing Sensor Data

When the sensor detects a new user they are registered in the machine. When it sees a known user again, it updates their skeleton and triggers userCallback.

Skeleton object properties:

| Property | Type | Description | | ----------------- | ------ | --------------------------------------------------- | | id | Number | Unique skeleton ID assigned by the sensor. | | joints | Array | 14 joint objects (see Joint Index Reference below). | | leftHandRaised | Number | 1 if left hand is above the head, otherwise 0. | | rightHandRaised | Number | 1 if right hand is above the head, otherwise 0. | | bothHandsRaised | Number | 1 if both hands are raised, otherwise 0. | | zoom | Number | Depth zoom factor. |

Joint object properties:

| Property | Type | Range | Description | | -------- | ------ | ----- | ------------------------------ | | x | Number | 0–640 | Horizontal position in pixels. | | y | Number | 0–480 | Vertical position in pixels. | | z | Number | 0–4 | Depth from sensor in meters. |

Joint Index Reference

| Index | Reference | Name | | ----- | -------------- | --------------- | | 0 | HAND_RIGHT | Right hand | | 1 | ELBOW_RIGHT | Right elbow | | 2 | SHOULDER_RIGHT | Right shoulder | | 3 | HAND_LEFT | Left hand | | 4 | ELBOW_LEFT | Left elbow | | 5 | SHOULDER_LEFT | Left shoulder | | 6 | HEAD | Head | | 7 | SPINE_NECK | Neck | | 8 | SPINE_CENTER | Center of spine | | 9 | HIP_CENTER | Center of hip | | 10 | KNEE_LEFT | Left knee | | 11 | KNEE_RIGHT | Right knee | | 12 | ANKLE_LEFT | Left ankle | | 13 | ANKLE_RIGHT | Right ankle |

skl.png

Use joint [8] SPINE_CENTER as the reference for user position — it is the most stable joint.

Basic joint access:

const machine = Ubox.setMachine({
  userCallback: (skeleton) => {
    const spine = skeleton.joints[8];
    console.log(`X: ${spine.x}, Y: ${spine.y}, Z: ${spine.z}`);
  },
});

Normalizing Joint Data

Raw joint coordinates are in sensor pixel space (640×480). Use normalize() to convert them to a [0, 1] range suitable for CSS, WebGL, or any other coordinate system.

const machine = Ubox.setMachine({
  userCallback: userFunction,
});

const sensor = Ubox.setSensor({ machine: machine });

function userFunction(skeleton, user) {
  const spine = skeleton.joints[8];
  const x = sensor.normalize(spine).x;

  document.getElementById("element").style.left = x * 100 + "%";
}

Grid Positioning

When mapZone: true is set, each User object gets a gridPos property with the zero-based index of the quadrant they currently occupy (left-to-right, top-to-bottom).

const sensor = Ubox.setSensor({
  machine: machine,
  mapZone: true,
  mapGrid: [2, 2], // 2 rows × 2 columns = 4 quadrants
});

function userFunction(skeleton, user) {
  // Quadrant layout for mapGrid: [2, 2]
  //  [ 0 | 1 ]
  //  [ 2 | 3 ]
  console.log("User is in quadrant:", user.gridPos);
}