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

@forgeax/engine-scene

v0.3.5

Published

Scene identity, hierarchy, and world transform propagation.

Readme

@forgeax/engine-scene

[!IMPORTANT] Owner: scene identity, hierarchy, and world-space propagation. This package is the sole authority for Transform, ChildOf, Children, Name, Mobility, and scenePlugin.

Smallest useful example

import { scenePlugin, Transform } from '@forgeax/engine/scene';
import { createWorldContext, World } from '@forgeax/engine/ecs';

const world = new World();
const context = await createWorldContext(world, [scenePlugin()]);
const entity = world
  .spawn({ component: Transform, data: {} })
  .unwrap();
void entity;
await context.fiber.restart();

scenePlugin() is a native Cordis plugin. Its Fiber installs hierarchy propagation and removes it when the realm unloads. The direct propagateTransforms(world) entry point returns Result<void, SceneError>; branch on error.code and repair a diagnosable stale edge, mirror mismatch, or cycle through World.set/World.removeComponent (or the owning structural command) before retrying while the World is healthy.

Keyed SceneAsset authoring

SceneAsset is authored as ordinary data. The key in entities is the only persistent local identity; runtime numeric slots and mount windows are derived inside this package and never belong in source files.

const level: SceneAsset = {
  kind: 'scene',
  entities: {
    root: { components: { Transform: {} } },
    house: {
      components: { ChildOf: { parent: 'root' } },
      instance: {
        source: houseSceneGuid,
        overrides: [{ target: ['door'], components: { Name: { value: 'front-door' } } }],
      },
    },
  },
};

| Source value | Meaning | |:--|:--| | 'root' | Entity in the current scene | | ['house', 'door'] | Entity door inside instance house; every segment is a key | | sceneEntity(guid, address) | Reference bound to one catalog identity and address | | sceneEntity('', address) | Reference to an anonymous direct POD scene |

[!IMPORTANT] Unknown components, unknown fields, missing targets, hierarchy cycles, and recursive instance graphs fail before entity creation. A direct POD scene has no persistent source identity; choose a Pack output key before saving it.

Publication fence for loaded scenes

Catalog-loaded scenes carry their publication evidence in the producer/loading context, not in the authored instance declaration. Acquire the current fence from that context, then pass it to AssetRegistry.instantiateWithPublicationFence or instantiateFlatWithPublicationFence; the call compares the complete tuple: source path and revision, generation, package digest, output-set digest, and receipt identity. Ordinary instantiate/instantiateFlat still validate every nested scene output against the parent publication before spawning. A missing or mismatched tuple returns asset-generation-fence-mismatch with expected, actual, currentGeneration, lastKnownGood, and recovery actions; repair or recook the producer, then open the current publication and retry. Direct anonymous POD scenes have no Catalog fence and remain addressable only for their current in-memory instance; saving one requires an explicit Pack/output key.

Each SceneInstanceState retains the schema-declared shared references in its source, including arrays and instance overrides, until that state is destroyed. Changing a live field therefore does not invalidate the original value needed by worldRemoveSceneOverride. Nested instances own their source references independently; flat top-level entities retain only their live component values. Replacing an owned scene root can release the old instance without affecting another consumer of the same asset.

When the plugin runs from the scheduled path, world.update(deltaSeconds) wraps a thrown Scene failure as system-failed; the wrapper's error.detail.cause is the original SceneError (including its structured code/detail). That post-write system failure poisons the World, so the next use returns world-poisoned. Inspect detail.cause, stop using that World identity, discard it, and ask the App execution owner to call app.execution.rebuild() for a fresh World. Do not retry a poisoned or partially-written World in place.

If an explicitly malformed internal fixture reports a Children mirror mismatch, never write the target array directly. A same-target world.set(child, ChildOf, { parent }) intentionally records the source evidence but skips mirror/index churn, so it cannot repair that mismatch. On a healthy World, repair through the source owner by removing and re-adding ChildOf (or reparenting through another target and then back to the intended one); if the failure has poisoned the World, discard it and rebuild through the App instead.

Transform propagation writes the exact recomputed frontier into GlobalTransform and advances those rows' ordinary component versions only after a successful pass. A no-change pass advances no GlobalTransform row. Persistent consumers keep a changed: [GlobalTransform] Query, so a moved subtree is exposed as contiguous spans without a parallel event journal. Transform remains authored local TRS; GlobalTransform is the resolved world-space authority.

The first propagation, and the next one after any hierarchy error, validates the full hierarchy through ECS-maintained Children lists. After that, work is driven by change evidence: edited Transform/ChildOf/GlobalTransform rows, plus the occupants of each 256-row storage block whose membership changed (spawn, despawn, component add/remove). Only the highest such entities and their subtrees are expanded in parent-first order, so an unrelated spawn or despawn costs the touched blocks, not the Transform population. A ChildOf value write whose target Children list does not mirror the edge, a parent walk longer than the hierarchy, or an entity that leaves the Transform pair while still holding a hierarchy edge falls back to the full pass so malformed graphs report the same diagnostics as a cold evaluation. A second FixedUpdate/Update propagation without intervening writes performs no hierarchy traversal. External writes to GlobalTransform (flat roots included) are reconciled back to the derived value; the owner's own publications are consumed before returning. Children remains a read-only relationship projection; there is no duplicate Scene graph or per-node matrix cache.

Removing ChildOf is a structural root transition. Even when the authored Transform is unchanged, the next propagation composes and publishes that entity as a flat local root; ordinary same-value hierarchy recomputation still does not publish an unchanged GlobalTransform row.

Flat propagation uses the same numeric kernel inline or through an installed SharedKernel executor. It borrows the existing changed query, joins root matrices before resolving the hierarchy, and leaves small or fragmented ranges inline. Scene builds its self-contained kernel module into pkg/ from the same TypeScript source; no new Worker pool, component-name registry, or transform change journal is introduced. Partial shared writes poison the owning World.

Transform declares the generic ECS requirement GlobalTransform. Therefore ordinary world.spawn, world.addComponent, and deferred Commands.spawn materialize the transient world column automatically at the structural boundary; scene code does not need a special pair-completion helper and the frame loop never scans entities to repair them. Explicit GlobalTransform data remains valid when an importer or recovery path needs to provide it.

ChildOf declares Transform as a relationship-source requirement. A child created with only { component: ChildOf, data: { parent } } therefore receives the full local/world pair transitively, while explicit Transform data still wins. This keeps hierarchy authoring small without adding per-frame repair or an additional scene-side component registry.

Deferred Commands.spawn uses the same { component, data } entries as World.spawn; the pending child is materialized and linked at command flush. For an existing child, the generic reparent call is world.reparent(child, newParent, ChildOf, { parent: newParent }) and routes through the same relationship owner.

ChildOf uses linkedSpawn: true: despawning a parent recursively despawns its linked hierarchy. A generic relationship may opt out, but that is not the Scene hierarchy lifecycle.

Removing the required component is still an explicit malformed-state escape hatch, not an automatic cascade. Propagation reports that state as a structured SceneError, preserving a clear owner and recovery path.

The authority also applies to dynamic loading: import Transform, ChildOf, and scenePlugin from @forgeax/engine/scene when a host resolves packages at runtime.

Mobility

Mobility { kind } is the author's commitment about motion, stored as the closed enum MobilityKind = 'static' | 'stationary' | 'movable' (numeric values in MobilityKindValue, narrowed by mobilityKindFromU32). An entity without Mobility is movable; runtime spawns stay movable unless authoring writes static.

world.spawn(
  { component: Transform, data: { pos: [0, 0, -5] } },
  { component: Mobility, data: { kind: MobilityKindValue.static } },
);

| Kind | Mesh entity | Light entity | |:--|:--|:--| | static | Never moves after placement; eligible for baking and static caches (a shadow caster joins the static shadow layer on its first frame) | Fully baked | | stationary | Invalid: mobility-invalid-kind | Fixed position, runtime direct light | | movable / absent | No promise; caches use observed motion | Fully realtime |

Mobility is the rendering and baking authority; physics RigidBodyType is the simulation authority. Neither is derived from the other.

Violations are non-blocking diagnostics with one closed union, MobilityDiagnosticCode in src/errors.ts. Each diagnostic carries code, expected, hint, and detail.entity, plus detail.sceneEntityRef when a SceneInstance binds the entity. Each (World, code, entity) is reported once through subscribeMobilityDiagnostics(listener) and console.warn. The package that sees both sides of each contract detects it:

| Code | Detector | Trigger | |:--|:--|:--| | mobility-static-moved | scenePlugin Update system | A static entity's Transform changes after the frame that declared it | | mobility-invalid-kind | Renderer source systems | stationary on an entity with MeshFilter | | mobility-physics-conflict | physicsPlugin | static with RigidBody type dynamic or kinematic |

Diagnostics never change behaviour: a moved static entity still propagates and renders correctly. Repair by declaring MobilityKindValue.movable (or removing Mobility) on entities that move. Detector packages report through emitMobilityDiagnostic(world, entity, violation); they do not define another channel.

Boundary

| This package owns | Excluded concepts | |:--|:--| | Identity, parent/child links, local/world transforms | Meshes, materials, cameras, skins, animation, GPU/RHI |

See src/index.ts for the public roster and src/errors.ts for recovery details.

Visibility hierarchy boundary

Quick start: create ChildOf links through the scene package, then let resolveVisibility(world) consume the projected hierarchy when a render or remote diagnostic asks for an effective state.

| Fact | Scene owns | Consumer owns | |:--|:--|:--| | Parent relation | ChildOf, Children, and hierarchy projection | Visibility intent and render filtering | | Effective lookup | Valid parent traversal and hierarchy diagnostics | Visibility field values and renderer statistics | | Recovery | Repair a stale/cyclic relation from SceneHierarchyDiagnostic | Do not reinterpret a hierarchy error as a camera or picking error |

Read VisibilityResolution.source to distinguish self, parent, and the default root case. If diagnostics report an invalid hierarchy, fix the scene relation and resolve again; do not add a render-only parent or bypass the scene graph. Camera, picking, lifecycle, assets, and VFX shadow behavior are out of scope for this package.