Skip to content

@wity/scene-core ​

The full headless engine. Parse, evaluate, serialize, and validate wity-scene documents.

Install ​

bash
npm install @wity/scene-core

For Node.js environments, also install the optional XML peer:

bash
npm install @xmldom/xmldom

parse(xml) ​

Parse a wity-scene XML string into a WityScene object.

ts
parse(xml: string): WityScene

Throws if the XML is malformed or the root element is not <wity-scene>.

js
import { parse } from '@wity/scene-core';
const scene = parse(xmlString);
// → { version: '1.0', width: 1920, height: 1080, dur: 8, layers: [...], cast: [...] }

evaluate(scene, t) ​

Evaluate a scene at time t (seconds), returning the full render frame.

ts
evaluate(scene: WityScene, t: number): ComputedFrame

t is clamped to [0, scene.dur]. Pure function — no mutation.

js
import { evaluate } from '@wity/scene-core';
const frame = evaluate(scene, 1.5);
// → { t: 1.5, width: 1920, height: 1080, elements: [...] }

Note: ws-audio elements appear in frame.elements with visible set correctly for their temporal window but produce no pixel output. ws-note and ws-hotspot elements are excluded from ComputedFrame entirely (evaluate returns null for them). ws-character entities are not in elements — access them via scene.cast.


serialize(scene) ​

Serialize a WityScene back to a canonical XML string. Round-trips cleanly with parse().

ts
serialize(scene: WityScene): string
js
import { serialize } from '@wity/scene-core';
const xml = serialize(scene);
// → '<?xml version="1.0" encoding="UTF-8"?>\n<wity-scene ...'

The <ws-cast> section is serialized before layers if scene.cast is non-empty. <ws-shader> elements are serialized at the scene root (after layers). <ws-link> elements are serialized at the scene root (after shaders).


validate(scene) ​

Validate a WityScene object without throwing.

ts
validate(scene: WityScene): { valid: boolean, errors: string[], warnings: string[] }
js
import { validate } from '@wity/scene-core';
const result = validate(scene);
if (!result.valid) {
  console.error(result.errors);
}

resolveUnit(value, containerSize) ​

Resolve a unit value string to pixels.

ts
resolveUnit(value: string | number, containerSize: number): number
js
import { resolveUnit } from '@wity/scene-core';
resolveUnit('50%', 1920)  // → 960
resolveUnit('120px', 0)   // → 120
resolveUnit(80, 0)        // → 80

Types ​

All JSDoc types are in schema/types.js and re-exported from the package root.

WityScene ​

js
{
  version:  string,       // "1.0"
  width:    number,       // canvas width px
  height:   number,       // canvas height px
  dur:      number,       // total duration seconds
  layers:   WsLayer[],
  cast:     WsCharacter[], // semantic entities (non-rendered)
  shaders?: WsShader[],   // scene-defined shader resources (referenced by ws-vfx)
  links?:   WsLink[],     // interactive navigation edges (DAG); consumed by quest engine
}

WsLayer ​

js
{
  id:       string,
  label?:   string,   // human-readable display name; falls back to id in UIs
  z:        number,
  opacity:  number,   // 0–1
  elements: WsElement[],
  gate?:    string,   // conditional visibility variable; evaluated by interactive engines
}

WsElement ​

WsText | WsRect | WsImage | WsVideo | WsAudio | WsCarousel | WsNote | WsHotspot

All visual elements (WsText, WsRect, WsImage, WsVideo) share WsElementBase:

js
{
  id, x, y, anchor, begin, dur, z, opacity,
  animateIn, animateOut, animateDur,
  animateEasing?,  // custom cubic bezier: "x1,y1,x2,y2" — overrides built-in preset easing
  name?,
  gate?,           // conditional visibility variable; evaluated by interactive engines
}

WsAudio has a minimal base: { id, begin, dur } — no spatial attributes.

WsKeyframe ​

Keyframe children of WsText, WsRect, and WsImage. Animate position and/or opacity over element-relative time.

js
{
  t:        number,        // element-relative time in seconds (from element's own begin)
  x?:       string | number,  // horizontal position at this keyframe (unit value)
  y?:       string | number,  // vertical position at this keyframe (unit value)
  opacity?: number,        // opacity 0–1 at this keyframe
  easing?:  string,        // cubic bezier for segment FROM this keyframe: "x1,y1,x2,y2"
}

Only specified properties are animated; unspecified fall back to the element's static attribute value. Keyframes are sorted by t and interpolated linearly by default.

WsText ​

js
WsElementBase & {
  tag:           'ws-text',
  content:       string,
  fontSize:      string | number,
  fontFamily:    string,
  fontWeight:    string,
  color:         string,
  textAlign:     'left' | 'center' | 'right',
  lineHeight:    number,
  maxWidth:      string | number | null,
  letterSpacing: string | number,
  keyframes?:    WsKeyframe[],
}

WsRect ​

js
WsElementBase & {
  tag:         'ws-rect',
  width:       string | number,
  height:      string | number,
  fill:        string,
  stroke:      string | null,
  strokeWidth: number,
  rx:          number,
  keyframes?:  WsKeyframe[],
}

WsImage ​

js
WsElementBase & {
  tag:        'ws-image',
  src:        string,
  width:      string | number,
  height:     string | number,
  fit:        'cover' | 'contain' | 'fill' | 'none',
  keyframes?: WsKeyframe[],
}

WsCarousel ​

A multi-source media carousel that cycles through images/videos with configurable layout modes.

js
WsElementBase & {
  tag:        'ws-carousel',
  width:      string | number,
  height:     string | number,
  fit:        'cover' | 'contain' | 'fill' | 'none',
  layout:     string,          // 'dissolve' | 'before-after' | 'media-wall' | 'pdp-mockup'
  interval:   number,          // transition interval in ms
  autoplay:   boolean,
  sources:    WsSource[],
  keyframes?: WsKeyframe[],
  vfx?:       WsVfx[],
}

WsSource ​

A media source entry inside a WsCarousel.

js
{
  src:  string,                // media file URL
  type: 'image' | 'video',    // default 'image'
}

WsVideo ​

js
WsElementBase & {
  tag:     'ws-video',
  src:     string,
  width:   string | number,
  height:  string | number,
  fit:     'cover' | 'contain' | 'fill' | 'none',
  volume:  number,         // 0–1
  trimIn:  number,         // seconds into source file
  trimOut: number | null,  // seconds into source file; null = no trim
  muted:   boolean,
  cues?:   WsCue[],        // optional timed speech/subtitle cues
}

WsAudio ​

js
{
  tag:     'ws-audio',
  id:      string,
  begin:   number,
  dur:     number,
  src:     string,
  volume:  number,         // 0–1
  loop:    boolean,
  trimIn:  number,
  trimOut: number | null,
  name?:   string,              // optional human-readable display name
  cues?:   WsCue[],             // optional timed speech/subtitle cues
}

WsNote ​

Semantic annotation metadata attached to a layer — non-rendered. Consumed by presentation layers (slideshow labels, export captions, accessibility). Multiple ws-note elements per layer form the semantic description of that page/slide.

js
{
  tag:     'ws-note',
  id:      string,
  role:    string,             // 'headline' | 'tagline' | 'description' | 'caption' | 'cta' | 'stat'
  content: string,
  target?: string,             // element id this note annotates
  href?:   string,             // URL (for CTA role)
  name?:   string,
}

WsHotspot ​

An interactive hotspot region — a tappable/clickable area within a layer. Non-visual: standard renderers ignore it. The interactive engine evaluates hotspot actions on user tap.

js
WsElementBase & {
  tag:     'ws-hotspot',
  width:   string | number,
  height:  string | number,
  action:  string,             // 'goto' | 'set-var' | 'animate'
  target?: string,
  var?:    string,
  value?:  string,
  label?:  string,
}

WsVfx ​

Visual effect descriptor nested inside any visual element. Multiple ws-vfx children form an ordered effect chain. Use preset for built-in effects or shader for scene-defined shaders (mutually exclusive).

js
{
  preset?:    string,          // built-in effect name (e.g. "glitch", "rgbShift")
  shader?:    string,          // scene-defined shader id (references ws-shader)
  intensity?: number,          // 0–1 (default 1)
  speed?:     number,          // animation speed multiplier (default 1)
  enabled?:   boolean,         // default true
  uniforms?:  object,          // arbitrary shader uniform overrides
}

WsShader ​

A user-defined shader resource declared at scene root. Renderers resolve ws-vfx shader references against these definitions.

js
{
  id:        string,           // unique identifier, referenced by ws-vfx shader attr
  lang:      string,           // 'glsl' | 'wgsl'
  source:    string,           // shader source code
  uniforms?: WsUniformDecl[],  // declared uniforms with type/range metadata
}

WsUniformDecl: { name, type, default?, min?, max? }

A scene-level navigation edge defining a transition in the interactive DAG. Non-rendered metadata consumed by the quest engine.

js
{
  from:       string,          // source layer id
  to:         string,          // destination layer id
  trigger?:   string,          // hotspot id that activates this link
  condition?: string,          // variable name that must be truthy
  label?:     string,          // human-readable edge label (for graph UI)
}

WsCue ​

Timed speech/subtitle metadata nested inside WsVideo or WsAudio. Non-rendered — consumed by analysis services, AI agents, and accessibility tools. Timestamps are relative to the source media file.

js
{
  begin:    number,        // start time within source media (seconds)
  end:      number,        // end time within source media (seconds)
  text:     string,        // speech/subtitle text content
  speaker?: string,        // optional ws-character id (links to scene.cast)
}

WsCharacter ​

js
{
  id:           string,
  name:         string,
  role?:        string,
  description?: string,
  avatarUrl?:   string,
}

ComputedFrame ​

js
{
  t:        number,             // time this frame was computed for
  width:    number,
  height:   number,
  elements: ComputedElement[],  // sorted by z ascending
}

ComputedElement ​

js
{
  id:      string,
  tag:     'ws-text' | 'ws-rect' | 'ws-image' | 'ws-video' | 'ws-audio' | 'ws-carousel',
  x:       number,    // pixels, anchor-adjusted + animation offset (visual elements)
  y:       number,
  opacity: number,    // element × layer × animation opacity
  z:       number,    // layer.z * 1000 + el.z
  visible: boolean,   // false outside [begin, begin+dur]
  props:   object,    // tag-specific resolved props
  content: string | null,  // text content (ws-text only)
  vfx?:    WsVfx[],  // VFX effect chain (visual elements only)
}

ws-note and ws-hotspot elements are excluded from the computed frame — they are non-visual metadata consumed by presentation layers and interactive engines respectively.