A screen effect plays in an overlay the host app places above its own screens — no camera, no face. It is a product of its own: this manifest is independent of the face lens manifest (manifest-v1.md), with its own enums, schema, fixtures and rendering rules (effect-rendering-v1.md). Both manifests ship in the git tag format-v1.0.0.
manifest-v1.md
effect-rendering-v1.md
format-v1.0.0
{ "formatVersion": 1, "effectId": "<uuid>", "version": 1, "minEngineVersion": 1, "durationMs": 2500, "layers": [], "triggers": [], "animations": [], "assets": {} }
durationMs
layers
triggers
animations
assets
<name>_<first 8 hex of sha256>.png
{ sha256, bytes }
type
fit
sheet.endBehavior
visible
show
play sprite
motion
source
count
{ "type": 6, "animation": "<clip id>" }
property
easing
endBehavior
Lifecycle names: effect.started, effect.ended. Host names: any ^[a-z][a-z0-9_.-]{0,63}$ — the platform defines the mechanism, the customer the vocabulary. Triggers have no threshold.
effect.started
effect.ended
^[a-z][a-z0-9_.-]{0,63}$
threshold
Every layer has id (^[a-z][a-z0-9_-]{0,31}$, unique) and visible.
id
^[a-z][a-z0-9_-]{0,31}$
asset
origin {x, y}
emitter { ratePerSecond 0–300, lifetimeMs 100–10 000, speed 0–2, directionDeg −180–180, spreadDeg 0–360, sizeScale 0.01–1, gravity −2–2 }
burstCount
maxParticles
sheet { frameWidth, frameHeight 1–1024, frameCount 1–256, fps 1–60, loop, endBehavior }
startMs
intensity
frequencyHz
position {x, y}
scale
rotationDeg
opacity
sheet
position
Lengths (particle speed, gravity, sizeScale, image and sprite scale) are multiples of S = min(overlay width, overlay height).
speed
gravity
sizeScale
S = min(overlay width, overlay height)
A clip animates one layer over its own time; the rendering rules are effect-rendering-v1.md §E10.
{ "id": "bounce-in", "layer": "badge", "durationMs": 800, "loop": false, "autoplay": true, "startMs": 0, "endBehavior": 1, "tracks": [ { "property": 3, "keys": [{ "atMs": 0, "value": 0.1, "easing": 6 }, { "atMs": 800, "value": 0.5, "easing": 1 }] } ] }
layer
loop
autoplay
tracks
keys
atMs
Properties each layer type may animate, and each property's key range:
position.x
origin.x
position.y
origin.y
Motion layers (3) animate nothing. Tables in code: EFFECT_ANIMATABLE_PROPERTIES, EFFECT_ANIMATION_VALUE_RANGE, MAX_ANIMATIONS (16), MAX_TRACKS_PER_ANIMATION (6), MAX_KEYS_PER_TRACK (32). Every animation feature is engine level 1.
EFFECT_ANIMATABLE_PROPERTIES
EFFECT_ANIMATION_VALUE_RANGE
MAX_ANIMATIONS
MAX_TRACKS_PER_ANIMATION
MAX_KEYS_PER_TRACK
packages/format/schema/effect.v1.schema.json
packages/format/fixtures/effect/valid/
packages/format/fixtures/effect/invalid/
description
base
fixtures/effect/valid/
.json
patch
add
remove
replace
expectedIssues
rejectedBySchema: true
confetti.json
background-sprite.json
trophy.json
animated.json
Semantic rules run only on a schema-valid manifest. Only the path + code of a semantic issue are normative; a schema failure only has to be rejected.
path
code
layer.duplicate_id
/layers/i/id
asset.missing
/layers/i/asset
asset.unused
/assets/<name>
package.too_large
/assets
assets[*].bytes
>
particles.too_many
/layers
timing.exceeds_duration
/layers/i/durationMs
startMs + durationMs > durationMs
/layers/i/startMs
startMs ≥ durationMs
trigger.unknown_event
/triggers/i/event/name
trigger.unknown_layer
/triggers/i/actions/j/layer
action_mismatch
trigger.unknown_animation
/triggers/i/actions/j/animation
animation.duplicate_id
/animations/i/id
animation.unknown_layer
/animations/i/layer
animation.property_not_animatable
/animations/i/tracks/j/property
animation.duplicate_property
animation.keys_out_of_order
/animations/i/tracks/j/keys/k/atMs
animation.key_outside_clip
animation.value_out_of_range
/animations/i/tracks/j/keys/k/value
animation.autoplay_conflict
/animations/i/autoplay
/animations/i/startMs
trigger.action_mismatch
/triggers/i/actions/j
engine.too_low
/minEngineVersion
minEngineVersion
Decode-time check, run by the API at validate and publish: asset.sheet_too_small — for a type 2 or type 5 layer, frameCount must not exceed floor(width / frameWidth) × floor(height / frameHeight) of the asset's actual pixel size.
asset.sheet_too_small
frameCount
floor(width / frameWidth) × floor(height / frameHeight)
formatVersion changes only on a breaking change to this document. Adding a layer type or an event source is not breaking: it raises that feature's engine level (EFFECT_ENGINE_LEVEL_BY_LAYER_TYPE, EFFECT_ENGINE_LEVEL_BY_EVENT_SOURCE in constants.ts), and a renderer compares minEngineVersion with its own engine version before it validates strictly. Until the first effect is published and the SDKs' 1.0.0 ship, every v1 change is part of the tag format-v1.0.0 (the tag is moved and the SDKs re-pin it).
formatVersion
EFFECT_ENGINE_LEVEL_BY_LAYER_TYPE
EFFECT_ENGINE_LEVEL_BY_EVENT_SOURCE
constants.ts