@wity/scene-core
The full headless engine. Parse, evaluate, serialize, and validate wity-scene documents.
Install
npm install @wity/scene-coreFor Node.js environments, also install the optional XML peer:
npm install @xmldom/xmldomparse(xml)
Parse a wity-scene XML string into a WityScene object.
parse(xml: string): WitySceneThrows if the XML is malformed or the root element is not <wity-scene>.
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.
evaluate(scene: WityScene, t: number): ComputedFramet is clamped to [0, scene.dur]. Pure function — no mutation.
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().
serialize(scene: WityScene): stringimport { 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.
validate(scene: WityScene): { valid: boolean, errors: string[], warnings: string[] }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.
resolveUnit(value: string | number, containerSize: number): numberimport { resolveUnit } from '@wity/scene-core';
resolveUnit('50%', 1920) // → 960
resolveUnit('120px', 0) // → 120
resolveUnit(80, 0) // → 80Types
All JSDoc types are in schema/types.js and re-exported from the package root.
WityScene
{
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
{
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 | WsHotspotAll visual elements (WsText, WsRect, WsImage, WsVideo) share WsElementBase:
{
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.
{
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
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
WsElementBase & {
tag: 'ws-rect',
width: string | number,
height: string | number,
fill: string,
stroke: string | null,
strokeWidth: number,
rx: number,
keyframes?: WsKeyframe[],
}WsImage
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.
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.
{
src: string, // media file URL
type: 'image' | 'video', // default 'image'
}WsVideo
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
{
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.
{
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.
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).
{
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.
{
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? }
WsLink
A scene-level navigation edge defining a transition in the interactive DAG. Non-rendered metadata consumed by the quest engine.
{
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.
{
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
{
id: string,
name: string,
role?: string,
description?: string,
avatarUrl?: string,
}ComputedFrame
{
t: number, // time this frame was computed for
width: number,
height: number,
elements: ComputedElement[], // sorted by z ascending
}ComputedElement
{
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.
