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

@waica/engine

v0.6.1

Published

Waica game engine core — archetype-driven, web-first, 2D & 3D

Downloads

1,143

Readme

@waica/engine

Waica's public engine core: entities and components, the game loop, scene and prefab loading, state machines, input, collisions, sprites, camera, stats, and UI.

import { Component, Game, loadScene } from '@waica/engine'

Component lifecycle

Waica keeps the lifecycle boundaries distinct:

  1. Entity.add() mounts a component and calls its onReady immediately. This remains component insertion order so setup behavior does not silently move.
  2. During each simulated frame, entities keep their existing entity order. When an entity's turn begins, Game snapshots and resolves that entity's component onUpdate schedule, then dispatches only that schedule.
  3. Physical onContact hooks run from DynamicBody while it updates. Hitbox onCollide hooks run after all entity component updates. Their existing component dispatch order is unchanged.
  4. Game.onUpdate callbacks run after component updates, collisions, and camera work; input end-of-frame handling follows them.
  5. Entity.destroy() calls onDestroy in component insertion order.

Only classes whose prototype chain implements onUpdate participate in the update schedule. Passive components remain available to their siblings but receive no update position.

Declaring update constraints

An updateable component can declare the sibling writes it must observe with inherited static updateAfter metadata:

import { Component, StateMachine } from '@waica/engine'

export class DamageFlash extends Component {
  static override componentName = 'DamageFlash'
  static override updateAfter: readonly string[] = ['StateMachine']

  override onUpdate(dt: number): void {
    const state = this.entity.get(StateMachine)?.current
    // This update observes StateMachine's state for the same frame.
    void state
    void dt
  }
}

The relation is conditional on co-presence. DamageFlash does not require a StateMachine; when that target is registered but absent from this entity, no edge and no issue are created. A subclass inherits updateAfter when it declares nothing and replaces the inherited list when it declares its own list.

Constraints always win over the tie-break. Whenever several components are ready simultaneously, Waica compares their case-sensitive componentName values in ascending Unicode code-unit order. It never uses locale collation, prefab order, scene order, or editor card grouping. Repeated names in one updateAfter list describe one edge.

Invalid schedules fail closed

Component identity must be unique within an entity, and both sides of a present constraint must implement onUpdate. Waica rejects duplicate component names, unknown targets, passive declarers or present passive targets, self-edges, and multi-component cycles.

At runtime an invalid entity runs no partial update schedule and does not fall back to authored order. Other entities continue updating. The engine logs one diagnostic containing the entity and causes, then logs again only if that entity's composition changes.

Tools can inspect a composition without constructing components:

import { resolveComponentUpdateSchedule, type ComponentClass } from '@waica/engine'

const registry: Record<string, ComponentClass> = { DamageFlash, StateMachine }
const result = resolveComponentUpdateSchedule(
  ['DamageFlash', 'StateMachine'],
  registry,
)

if (result.ok) {
  console.log(result.order) // ['StateMachine', 'DamageFlash']
} else {
  console.error(result.issues)
}

The resolver is pure. Pass the effective component-name list and the complete class registry, including project-owned classes. A valid result contains order and no issues; an invalid result contains typed, actionable issues and no executable order.

Runtime inspection

The engine owns Runtime Bridge protocol 1, but it is dormant during ordinary execution: there is no string-named global, network endpoint or per-frame bridge work. An MCP-owned browser context can install the symbol-keyed ephemeral activation hook before navigation. In that context Game.start() registers the fully constructed Game at a paused frame-zero baseline; Game.dispose() or page unload unregisters it.

Runtime Snapshots automatically inspect public own component fields and setter-backed accessors while excluding _ fields, entity, game and functions. A component can replace automatic discovery with the optional public contract:

class PathFinder extends Component {
  inspectState(): unknown {
    return { target: this.target, remaining: this.path.length }
  }
}

The return value still passes through the bounded safe projector; it is not serialized with arbitrary toJSON(). The package root exports the Runtime Snapshot, projection marker, metadata, control and activation types plus RUNTIME_BRIDGE_PROTOCOL_VERSION and RUNTIME_PROJECTION_LIMITS.