Skip to content

Components Reference

Auto-generated from engine source code — synced 2026-10-20

jEngine provides 93 built-in component types. All components derive Serialize and Deserialize for JSON scene files.

ComponentScene KeyDescription
Transformtransform2D transform: position, rotation, and scale.
WorldTransform—Cached world-space transform, derived from a [Transform] plus the chain of ancestor Transforms reached through [Parent] references.
Parent—Entity-parenting link.
BoneboneMarks an entity as a bone within a skeleton.
SkeletonRootskeleton_rootMarks the root entity of a skeleton.
IkTwoBone—Slice 3.
IkChain—A chain of any number of bones that bends to reach a target (Oct 2026; FABRIK - Godot’s SkeletonModification2DFABRIK, Unity’s 2D IK Chain).
IkGround—Foot IK against the scene’s colliders: where the clip’s floor is, and how far to look for ground above and below it.
IkLookAt—Slice 3.
MeshVertex—One vertex of a [Mesh2D].
Mesh2D—A textured polygon: what a sprite is, with the corners free.
SpriteComponentspriteSprite rendering component.
ScriptComponentscriptScript component — attaches a script to an entity.
Name—Human-readable name for an entity.
TextComponenttextText rendering component — renders text using a bitmap font.
TextOutline—An outline round world text (Oct 2026): the glyphs drawn again under themselves, grown by width world units, in color.
TextShadow—A shadow under world text (Oct 2026): the glyphs drawn again first, moved by offset (world units, y up) in color, and on a distance-field font blurred by softness world units - up to what the field encodes.
TextReveal—Where a typewriter is (TextComponent::reveal_speed): seconds since the text with this hash was set.
CustomComponents—Dynamic custom components — stores arbitrary user-defined component data.
TagstagsTags for entity grouping — scripts can query entities by tag.
PrefabInstanceprefab_instanceMarks an entity as an instance of a named prefab template.
CameraFollowcamera_followCamera follow component — makes the camera track this entity, or frame a whole set of them.
Camera2DComponentcameraCamera component — marks an entity as a game camera.
RenderLayersrender_layersWhich render layers this entity’s drawing is on, as a bitmask: a camera draws it when its cull_mask shares a bit (ecs::wgpu_systems::in_view_layers).
ColorLut—The frame’s colour grade as a look-up table (Oct 2026; Unity’s Color Lookup): a strip of n slices n x n - red right, green down, blue slice by slice - painted by grading a screenshot with the neutral strip (renderer::lut::identity) on it.
LightLayers—Which light layers an entity’s sprite is on, as a bitmask (Oct 2026; Godot’s light_mask, Unity’s sorting layers as a light’s targets): a point, spot or directional light lights it only when the light’s light_mask shares a bit.
SpriteMasksprite_maskA mask: what entities with masked naming this one are clipped to - a portal, a window, a frame (renderer::mask).
MaskedmaskedClips this entity’s drawing - its sprite, mesh, path, world text, or its emitter’s particles - to the sprite_mask on the entity named by; with outside, to everything but it.
RigidBody2Drigid_bodyRigid body component — attaches a physics body to an entity.
Collider2DcolliderCollider component — defines the collision shape for an entity’s body.
CharacterController2D—Character controller: moves a kinematic body the way a platformer or a top-down hero moves - by a velocity it asks for each frame, stopped by walls, sliding along them, climbing slopes up to max_slope_deg, stepping up ledges up to step_height, staying on the ground down slopes (snap_to_ground), carried by the kinematic platform it stands on, and standing on one-way platforms.
TilemapOccluder—Tilemap layers whose tiles cast shadows (Oct 2026; Godot’s TileSet occlusion layers, Unity’s TilemapShadowCaster).
TilemapCollider—Tilemap collision: which layers of the entity’s Tilemap are solid ground and which are one-way platforms, by layer name.
Joint2DjointJoint component — connects this entity’s body to another entity’s body.
AudioSourceaudio_sourceAudio source component — attaches a sound clip to an entity.
GuiComponentguiGUI component — attaches a `.
MaterialComponentmaterialMaterial component — assigns a named material to an entity for rendering.
PointLight2Dpoint_lightA point light that illuminates a radial area around the entity’s position.
DirectionalLight2D—A light from far away - the sun, the moon: every ray parallel, the same strength everywhere, and every shadow thrown the same way.
SpotLight2Dspot_lightA directional cone light attached to an entity.
AmbientLightambient_lightScene-wide ambient light.
AmbientOcclusion—Ambient occlusion: the ambient light darkened near the outlines of LightOccluders, as it is where two surfaces meet - the soft darkening at the foot of a wall, in a corner, between two boxes set close.
GlobalIllumination—Global illumination (Oct 2026): light that bounces.
ToonLighting—Toon lighting: the light that reaches each pixel is stepped into bands levels instead of falling off smoothly - cel shading.
LightmapComponentlightmapBaked lightmap: pre-computed light, added into the light buffer as a textured quad centred on the entity, before the scene is multiplied by it.
LightOccluderlight_occluderMarks an entity as a light blocker.
Conveyor—A moving surface (Oct 2026; Unity’s SurfaceEffector2D): whatever rests on this entity’s collider is carried along it at speed world units a second, along the entity’s own +x (turn the entity to aim it; negative runs the other way) - a conveyor belt, a moving walkway, a river’s surface, a treadmill.
SpriteCollider—A collider in the shape of its sprite (Oct 2026; Unity’s PolygonCollider2D made from a sprite, Godot’s “create collision polygon”): the outline of the sprite’s opaque pixels - traced as `light_occluder.
Velocity2Dvelocity2D velocity for simple kinematic motion (no physics engine needed).
BatchScriptComponentbatch_scriptStep 4 walking-skeleton: declares that this entity is processed by a batch script system.
BounceAreabounce_areaAxis-aligned bounding box that constrains entity position and bounces velocity.
Gravity2Dgravity2D acceleration vector applied to Velocity2D each frame (vel += g * dt).
DespawnOffscreendespawn_offscreenDespawn this entity once it has left the view.
Trail—A trail: a ribbon left behind the entity as it moves - a sword’s swing, a comet’s tail, a light painted with the mouse.
TrailPoint—One recorded point of a [Trail].
TouchControlstouch_controlsOn-screen controls for a touch screen: sticks and buttons that press keys.
TouchStick—A thumbstick pressing up to two of four directions (a diagonal is two).
TouchButton—A round button holding one key down while pressed.
LifetimelifetimeCountdown timer that fires on_expire once remaining <= 0.
Acceleration2DaccelerationPer-entity acceleration applied to Velocity2D each frame (vel += a * dt).
Drag2Ddrag2d2D velocity-proportional drag (vel *= (1 - drag * dt) per axis, clamped at zero).
Force2Dforce2d2D force on a unit mass: vel += force * dt, integrated by behaviors::update_forces (Sep 2026; batch-script vocabulary only before that).
Wind2Dwind2dAmbient 2D wind: vel += wind * dt, integrated by behaviors::update_forces (Sep 2026).
Impulse2Dimpulse2dInstantaneous 2D impulse: added to the velocity once and then cleared, by behaviors::update_forces (Sep 2026).
Torque2Dtorque2dSpin-up: `AngularVelocity.
MaxSpeedmax_speedClamps the magnitude of Velocity2D to at most value (pixels/sec).
OscillatoroscillatorSinusoidal positional motion.
FollowTargetfollow_targetMove toward target at up to max_speed (pixels/sec), stopping when within stop_distance.
SeekTargetseek_targetSteer Velocity2D toward target by accelerating at accel (pixels/sec²).
TweentweenInterpolate a single scalar property from from to to over duration.
PatrolPathRefpatrol_pathReferences a named route and walks the entity along it at speed (pixels/sec).
ShadowCastershadow_casterShadow caster component — renders a shadow silhouette behind the entity’s sprite.
VectorPathvector_pathVector path rendering component — defines 2D shapes via path commands.
InputAxis2Dinput_axisReads four named input actions (left/right/down/up) and produces a 2D axis vector each frame.
InputActionImpulseinput_action_impulseOne-shot impulse triggered when a named action is just pressed (edge-triggered, not held).
BehaviorGraphComponentbehavior_graphAttaches a behavior graph (`.
TriggerArea2Dtrigger_areaSpatial trigger volume that emits enter/exit/staying events into the sibling [TriggerOverlaps] component each frame.
TriggerOverlaps—Per-frame overlap state written by [update_trigger_areas] onto every entity that carries a [TriggerArea2D].
HealthhealthAuthoritative combat HP for the native damage systems.
HitboxhitboxAn offensive overlap hitbox.
DespawnOnDeathdespawn_on_deathMarker: entities carrying this are despawned by despawn_dead once their [Health::current] reaches 0.
ObstacleobstacleStatic circular blocker.
BulkEntity—Marks an entity spawned in bulk by a system, rather than authored or individually meaningful.
HealthBarhealth_barA floating health bar drawn above an entity that also carries a [Health].
HealthBarView—Runtime-only back-link from a health-bar sprite to its owner.
NameTagView—Runtime-only back-link from a floating name-tag text entity to its owner.
WrapBoundswrap_boundsWraps `Transform.
ClampBoundsclamp_boundsClamps `Transform.
DespawnOutOfBoundsdespawn_out_of_boundsMarks an entity for despawn when its `Transform.
FaceVelocityface_velocityRotates `Transform.
AngularVelocityangular_velocityConstant angular velocity (radians/sec) applied to `Transform.
FlickerflickerPeriodically toggles `SpriteComponent.
ShakeshakeTransient additive jitter offset applied to `Transform.

2D transform: position, rotation, and scale.

Scene JSON key: transform

FieldTypeDescription
positionVec2
rotationf32Rotation in radians.
scaleVec2

Cached world-space transform, derived from a [Transform] plus the chain of ancestor Transforms reached through [Parent] references. Recomputed each frame by [crate::ecs::hierarchy::resolve_world_transforms]. Sprite rendering uses this when present; behavior systems and physics still mutate the local [Transform]. A world transform is a full 2D affine map: a local point p lands at position + R(rotation) * K(shear) * (scale * p), where K(k) is [[1, k], [0, 1]]. shear is zero unless a turned child sits under a non-uniformly scaled (or sheared) parent - a rune circle spinning on a floor squashed into perspective. Before Sep 2026 there was no shear, so such a child turned as a whole ellipse instead of turning within its parent’s squash (Godot’s Transform2D and Unity’s matrices both shear). See [crate::ecs::hierarchy::compose]. Foundation slice of docs/feature-backlog.md item 3.5; the bone / animation / Spine slices build on top.

FieldTypeDescription
positionVec2
rotationf32Rotation in radians.
scaleVec2
shearf32Shear between the scale and the rotation (see the type’s docs).

Entity-parenting link. Attaching this to a child entity tells the hierarchy system to compose the child’s local [Transform] under the parent’s [WorldTransform] each frame. Stored as a hecs::Entity for direct lookup at runtime. Scene serialization writes it out as a name reference (the parent entity’s name field) and resolves back to an Entity on load — see crate::scene::format::load_scene. Cycles are detected and broken at resolution time (a cycle leaves the child’s WorldTransform equal to its local Transform for that frame and emits a log::warn!).

Newtype wrapper around hecs::Entity.


Marks an entity as a bone within a skeleton. Bones are ordinary parented entities (slice 3.5.A) carrying a [Transform]. This component layers the skeleton-specific metadata on top: - name: stable identifier for lookup via [crate::ecs::skeleton::find_bone] and animation clips (slice 3.5.C). Unique per skeleton; scripts and animations resolve bones by name. - length: nominal length of the bone in local units, used for IK targeting (slice 3.5.F), gizmo drawing, and mesh skinning weight defaults (slice 3.5.E). 0.0 is allowed for tip bones. A bone’s parent in the skeleton tree is the standard Parent component — there is no separate parent_bone field. Foundation for the Spine / skeletal animation stack (docs/feature-backlog.md item 3.5, slice B).

Scene JSON key: bone

FieldTypeDescription
nameString
lengthf32

Marks the root entity of a skeleton. The root is itself an ordinary entity with a [Transform] (often representing the character’s pelvis or rig origin). Its descendant entities carrying [Bone] components form the skeleton tree. name identifies the skeleton instance for tooling and scripts; it is independent of the entity name to allow multiple skeletons of the same kind in a scene.

Scene JSON key: skeleton_root

FieldTypeDescription
nameString

Slice 3.5.F — two-bone IK constraint attached to the end (effector) bone of a 3-bone chain root → mid → end. The solver walks Parent twice from the entity carrying this component to find the mid and root bones, then rotates root.Transform.rotation and mid.Transform.rotation (in local space) so that the chain’s tip reaches target (world-space). Bone lengths come from the Bone component’s length field. flip_bend selects the “elbow up” vs “elbow down” solution. Scaled and mirrored rigs work (Sep 2026, BUGS #152): the lengths are scaled by each bone’s world scale.x, and under a mirrored parent (scale.x * scale.y < 0, the usual way a 2D character turns round) flip_bend keeps its meaning on screen - the knee goes to the mirrored side, as the rest of the character does. weight (Sep 2026) blends the solve over the animated pose: 0 leaves the clip’s leg alone, 1 is all IK - so a foot can be planted and released. ground plants an animated foot on what is under it: the target is then not a script’s point but the foot where the clip put it, moved up or down onto the colliders beneath (ecs::outlines) by as much as the ground there is above or below the clip’s own floor. A walk authored on flat ground walks up a slope; the body is the game’s to raise.

FieldTypeDescription
target[f32; 2]
flip_bendbool
weightf32How much of the solve to apply, 0..1.
groundOption<IkGround>Plant the animated foot on the ground under it (see [IkGround]).

A chain of any number of bones that bends to reach a target (Oct 2026; FABRIK - Godot’s SkeletonModification2DFABRIK, Unity’s 2D IK Chain). On the chain’s end bone: bones counts up the parents from it, so 4 bends this bone and the three above it. The tip - the end bone’s far end - goes to target, or to the entity named target_name when that is set. max_bend limits each joint after the first to that many radians either way from where the animation put it (pi is free), which is what keeps a tail from folding back on itself. weight blends the solve over the animated pose, as two-bone IK’s does. Skeleton.set_ik_target / set_ik_weight reach it.

FieldTypeDescription
target[f32; 2]
target_nameString
bonesu32
iterationsu32
tolerancef32Close enough, in world units.
max_bendf32
weightf32
stateVec<(f32, f32)>Per bone, root first: the pose the solve bends from and the angle it last wrote. A bone still at what was written was posed by nothing else, so its rest stands; one moved by a clip takes the clip’s. Never saved.

Foot IK against the scene’s colliders: where the clip’s floor is, and how far to look for ground above and below it.

FieldTypeDescription
floorf32The height, in the skeleton root’s frame, of the floor the clip was authored on: a foot the clip puts here is standing on the ground.
reachf32How far above and below that floor to look for ground.
masku16The collision layers that count as ground (a collider’s collision_membership).

Slice 3.5.F — look-at IK constraint. Rotates the carrying entity’s local Transform.rotation so that its world +X axis (plus rotation_offset radians) points at target (world-space).

FieldTypeDescription
target[f32; 2]
rotation_offsetf32

One vertex of a [Mesh2D].

FieldTypeDescription
position[f32; 2]Local to the entity’s Transform: the mesh moves, turns and scales with its entity, and whatever deforms it writes only the shape.
uv[f32; 2]0..1 across the texture - or across the region, when the mesh names one - with v = 0 at the top, as a sprite’s uv_rect has it.
color[f32; 4]Multiplied with the component’s color. Per vertex, so a surface can darken toward its far edge without a second texture.
qf32Perspective weight, 1 for a flat mesh. For a quad that stands in depth - the side wall of a corridor - give the far edge’s vertices far / near (the ratio of the edge lengths) and the texture is mapped in perspective rather than bent along the quad’s diagonal. Anything not finite or not positive is drawn as 1.

A textured polygon: what a sprite is, with the corners free. Drawn in the sprite pass, sorted with the sprites, shadows and world text by z_order, batched with sprites of the same texture, and put through the entity’s MaterialComponent like a sprite. That is the difference from VectorPath (flat colour, its own pass, always over every sprite) and from SkinnedMesh (needs a skeleton, its own pass), and it is why a mesh can be a wall in a first-person corridor with a monster sprite standing in front of it. SpriteComponent::corners is the four-vertex, one-colour, affine-textured special case of this. Nothing in the engine writes the vertices: a package or a script does, per frame if it likes. That is the point - a corridor’s walls, a flag in the wind and a jelly are all “a system that writes a Mesh2D”. A mesh is never a backdrop: it is not drawn beneath the tilemaps however low its z, because the tilemap pass draws sprites only.

FieldTypeDescription
texture_idString
regionOption<String>A named region of that texture’s atlas, as SpriteComponent.region; resolved once into uv_rect by resolve_sprite_regions. Change it and clear uv_rect and region_resolved to re-point a pooled mesh.
uv_rectOption<[f32; 4]>The part of the texture the vertices’ uv span, [u0, v0, u1, v1]; the whole texture when absent.
region_resolvedbool
verticesVec<MeshVertex>
indicesVec<u32>A triangle list into vertices. Empty means a fan over the vertices in order, so a convex polygon - a quad - needs no indices. A pair of triangles a b c, a c d costs one sprite slot; any other triangle costs one of its own.
color[f32; 4]Multiplied with every vertex’s colour.
z_orderi32
sort_layerOption<String>

Sprite rendering component.

Scene JSON key: sprite

FieldTypeDescription
texture_idTextureIdAsset path or identifier for the texture - interned (asset::TextureId), so sorting and batching sprites by texture compares indices, not strings. Reads and writes as the string.
color[f32; 4]RGBA tint color (1.0 = full intensity).
z_orderi32Draw order — higher z_order draws on top, against sprites and other text alike: world text is drawn by the interleaved sprite pass since Sep 2026. Before that it was its own pass after every sprite pass, so a label at z 5 drew over a sprite at z 50. Tilemaps are still their own pass, before the sprites.
sizeOption<[f32; 2]>Sprite size in world units, before the transform’s scale. None draws at the texture’s own pixel size - the part of it the sprite shows, so a uv_rect, an atlas region or an animation frame draws at that cell’s size. See [SpriteComponent::drawn_size] for the exceptions. (Until Sep 2026 None always meant 64 x 64, whatever the texture; scenes written against that were migrated to an explicit [64, 64], so they look the same.)
uv_rectOption<[f32; 4]>UV rectangle for sprite atlas regions: [u_min, v_min, u_max, v_max]. Defaults to full texture [0, 0, 1, 1].
regionOption<String>Name of a region in the atlas registered under texture_id. When set, uv_rect is filled in from that region by [crate::ecs::systems::resolve_sprite_regions], so a sprite says which part of a sheet it draws by name rather than in coordinates that go silently wrong when the sheet is re-laid-out. The atlas has to be loaded (a scene’s atlas_refs); until then the sprite draws with whatever uv_rect it was given. The animation equivalent is AnimationFrame::region.
region_resolvedboolWhether region has been resolved. Runtime state: cleared by deserialization, so a loaded or spawned sprite resolves once.
anchor[f32; 2]Anchor/pivot point as a fraction of sprite size (0.0–1.0). [0.5, 0.5] = center (default). Position is placed at this point.
fillOption<SpriteFill>How the sprite’s size is filled with the source texture. None (default) and Some(SpriteFill::Stretch) both render a single quad covering size with UV [0,0,1,1] (modulated by uv_rect). See [SpriteFill] for NineSlice and Tile modes.
sort_layerOption<String>Optional sort-layer name. When set, the active .sortlayers asset’s base_z is added to z_order at render time.
y_sortboolSort this sprite against the others in its band by its world position instead of leaving them in an arbitrary order: lower on screen draws in front. This is what a game needs whenever y is standing in for depth - a beat-em-up’s walkable floor, an isometric or top-down scene where a character passes behind a tree. Without it the only order available is z_order, which is fixed at authoring time and so cannot say “whoever is nearer the camera right now”. It sorts within the band z_order and sort_layer put the sprite in, never across bands: a backdrop below the actors stays below them however far down the screen it is drawn. In one band a sprite that does not opt in sorts as though its y were 0, so a band mixing the two is only predictable if the plain sprites belong above or below everything - give them their own z_order instead. What sorts is the entity’s origin, not the middle of the quad, so anchor a character at its feet (anchor: [0.5, 0.0]) and it passes another exactly when they cross.
cornersOption<[[f32; 2]; 4]>Optional explicit quad corners as local-space offsets from the entity’s world position, in order [TL, TR, BR, BL]. When Some, the renderer draws this exact (possibly non-rectangular) quad and ignores size, anchor, fill, and rotation — used for faked perspective walls / trapezoids. The active uv_rect is still mapped corner-to-corner. Set from scripts via set_sprite_corners.
texture_pxOption<[f32; 2]>Pixel size of the texture named by texture_px_for, filled by [crate::ecs::systems::resolve_sprite_regions] once it is loaded. Runtime state, for [SpriteComponent::drawn_size].
texture_px_forTextureIdWhich texture texture_px was measured from, so a texture swap (set_sprite_region, an animation frame) is measured again.

Script component — attaches a script to an entity.

Scene JSON key: script

FieldTypeDescription
languageScriptLanguage
source_pathStringPath to the script source file (relative to project assets).
cull_distanceOption<f32>Optional cold-entity culling radius in world units. When Some(r), the script’s update() is skipped on frames where the entity is more than r units away from the active camera. init() and named callbacks (timers, events, GUI clicks) still fire normally — only the per-frame update is gated. None (default) disables culling and runs every frame, matching pre-culling behavior.

Human-readable name for an entity.

Newtype wrapper around String.


Text rendering component — renders text using a bitmap font.

Scene JSON key: text

FieldTypeDescription
textStringThe text string to display.
font_idStringPath to a .font asset, e.g. "fonts/ui.font". Empty uses the built-in font. The atlas image is the sibling file with the same stem (fonts/ui.font -> fonts/ui.png); a .font names no texture of its own. Each runtime scans the world for the ids its entities reference and loads them, so naming one here is the whole of the wiring - there is no scene-level refs array to keep in step. A font that fails to load falls back to the built-in face rather than dropping the text.
font_scalef32Font scale (1.0 = native pixel size).
color[f32; 4]RGBA tint color.
z_orderi32Draw order — higher z_order draws on top.
sort_layerOption<String>Optional sort-layer name (see SortLayers).
wrap_widthf32Wrap the text at this width in world units. 0.0 (the default) means no wrapping — only explicit newlines break the line, which is how text behaved before the layout pass existed.
alignTextAlignHorizontal alignment of each line about the transform’s x - see [TextAlign]. Written as "align": "center"; omitted means left, which is how every scene authored before Sep 2026 draws.
richboolRead text as markup - [color=#e05040], [b], [wave], [shake], [rainbow], [pause=0.5], [icon=sprites/key.png] (renderer::rich_text, Oct 2026). Off by default, so a string with brackets in it draws as it always did.
reveal_speedf32A typewriter: characters a second, waiting at each [pause], from whenever text last changed. 0 (the default) shows it all at once.
revealOption<TextReveal>The typewriter’s clock, kept by rich_text::tick_reveals; None until it first ticks, which shows the whole text.
outlineOption<TextOutline>An outline round every glyph ([TextOutline], Oct 2026).
shadowOption<TextShadow>A shadow under the text, offset and softened ([TextShadow]).
text_keyStringA string-table key (locale, Oct 2026): text is written from it in the current language, and again whenever that changes. Empty (the default) leaves text alone.
localizedu64The locale::stamp the text was last written for. Never saved.

An outline round world text (Oct 2026): the glyphs drawn again under themselves, grown by width world units, in color. On a distance-field font - the built-in one, or any .font made with jengine font-sdf - it is the same glyph with its edge moved out, so it is round at the corners and costs one more quad a glyph; a field encodes only so far past the ink (two layout pixels of the built-in font, times its scale), and a wider outline is cut to that. On a bitmap font it is eight offset copies.

FieldTypeDescription
widthf32How far past the glyph’s edge, in world units.
color[f32; 4]Linear RGBA.

A shadow under world text (Oct 2026): the glyphs drawn again first, moved by offset (world units, y up) in color, and on a distance-field font blurred by softness world units - up to what the field encodes.

FieldTypeDescription
offset[f32; 2]
color[f32; 4]
softnessf32

Where a typewriter is (TextComponent::reveal_speed): seconds since the text with this hash was set.

FieldTypeDescription
keyu64
elapsedf32

Dynamic custom components — stores arbitrary user-defined component data. Keys are component names (e.g. “Health”), values are JSON objects with fields.

Newtype wrapper around HashMap<String, serde_json::Value>.


Tags for entity grouping — scripts can query entities by tag.

Scene JSON key: tags

Newtype wrapper around std::collections::HashSet<String>.


Marks an entity as an instance of a named prefab template. 2.2 — Prefab variants / overrides. Authored in a scene as "prefab_instance": { "prefab_id": "Player", "overrides": { ... } }. At scene-load time, scene::prefab::expand_prefab_instances looks the template up in the runtime’s prefab map, merges its components onto the entity (any components the scene already authored win over the template), then applies dotted-path overrides on top. The component is retained on the spawned entity as a marker so scripts / tooling can ask “what prefab did this come from?”. A prefab can carry one too (Oct 2026): it is then a variant of the prefab it names - the base, its own components over it, then these overrides (scene::prefab::resolve_prefab_variants, which every runtime runs on its prefab map) - so a family of creatures is one prefab and its differences, and a variant of a variant works.

Scene JSON key: prefab_instance

FieldTypeDescription
prefab_idStringName of the prefab template to instantiate. Looked up in the runtime’s services.prefabs map (populated from assets/prefabs/*.prefab).
overridesHashMap<String, serde_json::Value>Per-instance property overrides keyed by dotted path (e.g. "sprite.color", "transform.position", or a top-level component key like "sprite" to replace the whole component). Values are raw serde_json::Values applied via serde_json::Value::pointer_mut-style traversal.

Camera follow component — makes the camera track this entity, or frame a whole set of them.

Scene JSON key: camera_follow

FieldTypeDescription
smoothingf32How fast the camera closes on its target, as a fraction of the remaining distance per frame at 60fps. Typical: 0.1-0.3. So larger is faster: 1.0 snaps instantly and 0.0 never moves at all. This doc comment used to say the opposite - “0 = instant snap, 1 = very slow follow” - which is exactly backwards, and the kind of error that costs an afternoon: a scene authored against it gets a camera that does not move and reads as a broken follow rather than a mistuned one. The arithmetic (1 - (1 - smoothing)^(dt * 60)) has always worked this way.
offsetVec2Camera offset from the entity position (in world units).
boundsOption<[f32; 4]>Optional bounds: [min_x, min_y, max_x, max_y]. Camera won’t move beyond these.
frame_tagOption<String>Frame every entity carrying this tag instead of following the one this component is on: centre on their bounding box and zoom out until it fits. Following one entity is the wrong shape for a whole genre. A two-player duel, a co-op screen, a boss and the player who is running from it - each has a set of things that must all stay visible, and none of them is served by picking one to centre on. The arcade needed it for a ship melee, where both ships are constantly running away from each other. With no tagged entity in the world it falls back to following this entity, so a scene does not go blank when the things it was framing are destroyed.
frame_paddingf32World units of margin around the framed set, so the things being framed are not flush against the edge of the screen.
zoom_rangeOption<[f32; 2]>[min, max] zoom while framing. Zoom here is the renderer’s: a larger number is closer in, so min is how far out the camera may pull and max is how far in it may push. The max matters more than it looks. Without a ceiling, two ships drifting together zoom the camera until the screen is two sprites and a wall of empty space, which is disorienting and hides everything either of them is about to run into.
dead_zone[f32; 2]A box round the middle of the view, [width, height] in world units, inside which the target moves without the camera following (Oct 2026; Godot’s drag margins, Cinemachine’s dead zone). Past its edge the camera is dragged along so the target sits on the edge. [0, 0] (the default) follows every step, as before.
look_aheadf32Look ahead: aim this many seconds ahead of where the target is going, by its velocity - so a runner sees what it runs into rather than what it left. 0 (the default) is off.
look_ahead_maxf32The furthest the look-ahead may lead, in world units.
look_ahead_smoothingf32How fast the look-ahead swings round when the target turns: a fraction of the way per frame at 60 fps, like smoothing. Slow, so a turn does not throw the view.

Camera component — marks an entity as a game camera. Multiple cameras can exist: the active one with no target and the highest priority draws the screen (ecs::camera_rig::main_camera), and one taking over from another can blend. Requires a Transform on the same entity.

Scene JSON key: camera

FieldTypeDescription
zoomf32Zoom level (1.0 = default). Higher values zoom in.
is_activeboolWhether this camera is the currently active (rendering) camera.
viewport_widthf32Viewport width hint (overridden at runtime by actual window size).
viewport_heightf32Viewport height hint (overridden at runtime by actual window size).
stretchboolStretch viewport_width x viewport_height over a screen of another shape, instead of showing more world along the longer axis (Camera2D::fit_view). Off by default: stretched, a square is drawn wide on a phone.
pixel_sizef32Draw the world at one pixel per pixel_size world units and scale it up - pixel art at its own resolution, on its own grid, scrolling smoothly (renderer::pixel_perfect). 0, the default, draws at the screen’s resolution. Script: Camera.set_pixel_size.
integer_scaleboolWith pixel_size, draw every art pixel as a whole number of screen pixels, showing a little more world to fill the screen (Camera2D::integer_view). Off by default: the authored area fills the screen exactly and an art pixel may be 4.27 screen pixels wide.
targetStringDraw into a texture of this name instead of onto the screen: a view (ecs::views). Any sprite, mesh or UI image naming it as its texture_id shows what this camera sees - a monitor, a minimap, one half of a split screen. Empty, the default, is the main camera.
target_size[u32; 2]The target texture’s size in pixels; 0 takes the screen’s.
cull_masku32Which render layers this camera draws (a bitmask against each entity’s render_layers, layer 1 for one with none). All of them by default - Unity’s culling mask, Godot’s cull mask: a minimap that shows markers the street views do not.
screen_rect[f32; 4]Draw onto this part of the screen (Oct 2026; Unity’s viewport rect): [x, y, w, h] as fractions of the screen from its top-left, y down - [0, 0, 0.5, 1] is the left half. The camera becomes a view drawing into a texture of its share’s size, shown by an image beneath every other UI (ecs::views::sync_screen_rects) - split screen with no target or UI to author. All zero, the default, is none.
priorityi32Which active camera draws the screen (Oct 2026; Cinemachine’s priority): the highest, the first of them on a tie. Raise one to hand it the view - Camera.set(e, "priority", 10).
blendf32Seconds the view takes to ease over to this camera when it takes over the screen from another, following its target the whole way. 0, the default, cuts.
confineVec<[f32; 2]>A polygon, in world units, this camera’s view stays inside - every edge of the screen, not only its centre (Cinemachine’s confiner): a gallery of any shape whose walls the view never looks past (ecs::camera_rig::confine). Empty, the default, is none.

Which render layers this entity’s drawing is on, as a bitmask: a camera draws it when its cull_mask shares a bit (ecs::wgpu_systems::in_view_layers). An entity without one is on layer 1 (bit 0), which every camera draws unless told not to.

Scene JSON key: render_layers

FieldTypeDescription
layersu32

The frame’s colour grade as a look-up table (Oct 2026; Unity’s Color Lookup): a strip of n slices n x n - red right, green down, blue slice by slice - painted by grading a screenshot with the neutral strip (renderer::lut::identity) on it. Applied in the output pass after the tonemap, mixed in by strength. The first one in the world is used.

FieldTypeDescription
texture_idStringThe strip’s texture.
strengthf320 is no grade, 1 the LUT’s.

Which light layers an entity’s sprite is on, as a bitmask (Oct 2026; Godot’s light_mask, Unity’s sorting layers as a light’s targets): a point, spot or directional light lights it only when the light’s light_mask shares a bit. A sprite without one is on layer 1 (bit 0), which every light lights unless told not to. Eight layers. The ambient light, lightmaps and freeform lights light every layer, and so is everything that is not a sprite - tilemaps, text, meshes, particles.

FieldTypeDescription
layersu32The layers, as bits: layer 1 is bit 0 (1), layer 2 bit 1 (2), and a sprite on both is 3.

A mask: what entities with masked naming this one are clipped to - a portal, a window, a frame (renderer::mask). The shape is in this entity’s space, so it moves, turns and scales with its transform.

Scene JSON key: sprite_mask

FieldTypeDescription
shapecrate::renderer::mask::MaskShape
featherf32The soft edge, in the shape’s own units; 0 is hard.

Clips this entity’s drawing - its sprite, mesh, path, world text, or its emitter’s particles - to the sprite_mask on the entity named by; with outside, to everything but it.

Scene JSON key: masked

FieldTypeDescription
byString
outsidebool

Rigid body component — attaches a physics body to an entity.

Scene JSON key: rigid_body

FieldTypeDescription
body_typeBodyType
massf32
gravity_scalef32
linear_dampingf32
angular_dampingf32
fixed_rotationbool
ccdboolSweep this body’s shape along its path instead of only testing where it landed - continuous collision detection. Rapier integrates velocity and then looks for overlaps, so a body that crosses more than its own thickness in one step can be past a wall by the time anything is measured: no contact is generated and it simply leaves. A 12-unit ball at 3000 units a second covers 50 units in a 60 Hz step. The engine has always constructed a CCDSolver and handed it to every step, but rapier only sweeps bodies that ask - so until this field existed the solver ran on an empty set for the life of the engine, and nothing could have used it. No game here had gone fast enough to notice. Off by default, and deliberately: the sweep is not free, and a scene that does not mention it behaves exactly as before. Turn it on for what is small and fast - a pinball, a bullet, a thrown weapon - and leave it off for crates and characters.
can_sleepboolWhether rapier may put this body to sleep when it stops moving. Sleeping is right for almost everything: a crate on a floor should cost nothing once it has settled. It is wrong for anything that is supposed to keep moving. A pinball that comes to rest for an instant on a steep slope is put to sleep there and stays - gravity does not wake a sleeping body, so it sits on a 53-degree ramp with a friction coefficient of 0.03 and never slides, which reads as the ball being glued to the wall. Defaults to true, which is rapier’s own default and the previous behaviour of every body in the engine.

Collider component — defines the collision shape for an entity’s body.

Scene JSON key: collider

FieldTypeDescription
shapeColliderShape
offset[f32; 2]
is_sensorbool
frictionf32
restitutionf32
densityf32
collision_membershipu16Collision layer membership bitmask — which layers this collider belongs to.
collision_filteru16Collision layer filter bitmask — which layers this collider can collide with.
one_wayboolA platform a [CharacterController2D] can jump up through and stand on: it blocks a character only from above, and one that asks to drop_through falls through it. Other bodies collide with it as usual.

Character controller: moves a kinematic body the way a platformer or a top-down hero moves - by a velocity it asks for each frame, stopped by walls, sliding along them, climbing slopes up to max_slope_deg, stepping up ledges up to step_height, staying on the ground down slopes (snap_to_ground), carried by the kinematic platform it stands on, and standing on one-way platforms. It applies no gravity and no acceleration of its own: that is the game’s feel, and belongs to the script or package setting velocity (see packages/jengine-platformer). The entity needs a kinematic RigidBody2D and a Collider2D; a capsule or a box with rounded corners slides best. The physics step fills in the rest of the fields after it moves the body. Scripts set the velocity with move_character and read the result with get_character.

FieldTypeDescription
velocity[f32; 2]The movement asked for this frame, in units per second.
skinf32The gap kept between the character and what it touches, in units.
max_slope_degf32The steepest slope it walks up, in degrees; anything steeper is a wall.
min_slide_degf32Slopes at least this steep slide it down, in degrees.
snap_to_groundf32How far down it looks for ground to stay on while walking down a slope or off a small step, in units. 0 turns it off.
step_heightf32The tallest ledge it steps up without jumping, in units. 0 turns it off.
step_widthf32The least room a ledge must have on top to be stepped onto, in units.
push_massf32Pushes dynamic bodies it walks into as if it weighed this much. 0 leaves them alone (they still block it).
drop_throughboolFalls through one-way platforms while set.
ride_platformsboolGoes down with a kinematic platform it stands on. (Being carried sideways and upward is rapier’s own doing and always happens.)
groundedboolStanding on something.
ground_normal[f32; 2]The normal of what it stands on (0, 1 on flat ground).
ground_entityOption<u64>The entity it stands on.
walli32-1 against a wall on its left, 1 on its right, 0 for none.
ceilingboolBumped its head this frame.
slidingboolSliding down a slope too steep to stand on.
moved[f32; 2]How it actually moved this frame, in units per second: velocity less what was blocked, plus what a platform carried.
touchingVec<u64>Every entity whose collider it overlaps or touches after moving, sensors included - pickups, hazards, doors.

Tilemap layers whose tiles cast shadows (Oct 2026; Godot’s TileSet occlusion layers, Unity’s TilemapShadowCaster). Each tile casts its tileset’s shape for it ([crate::tilemap::TileShape]: its occluder, else its collision outline, else the whole cell), and the cells are merged into a few long outlines before the lights see them ([crate::tilemap::occluder_loops]) - a wall of a hundred tiles is the four edges round it, not four hundred. The rest is a light_occluder’s: the same tint, opacity, self_shadow (off: rock lit by a lamp in the cave is lit itself, rock beyond it is in shadow), shadows_casters and shadow_length, applied to the whole map.

FieldTypeDescription
enabledboolWhen false the map casts no shadow.
layersVec<String>The layers that cast shadows, by name.
shadow_tint[f32; 3]As [LightOccluder::shadow_tint].
shadow_opacityf32As [LightOccluder::shadow_opacity].
self_shadowboolAs [LightOccluder::self_shadow].
shadows_castersboolAs [LightOccluder::shadows_casters].
shadow_lengthf32As [LightOccluder::shadow_length].
cachecrate::tilemap::shapes::OutlineCacheThe merged outlines, kept until the tiles or shapes change. Never saved.

Tilemap collision: which layers of the entity’s Tilemap are solid ground and which are one-way platforms, by layer name. The physics step builds one static body for the tilemap from its tiles, merged into as few boxes as it can, and rebuilds it when those tiles change (a block broken with tilemap_set_tile). A layer flagged collision (Tiled names it so) is solid whether it is listed or not, and a tilemap with such a layer collides even without this component.

FieldTypeDescription
solidVec<String>Layers whose every tile is solid.
one_wayVec<String>Layers whose tiles are one-way platforms (see Collider2D::one_way).

Joint component — connects this entity’s body to another entity’s body.

Scene JSON key: joint

FieldTypeDescription
target_entityStringName of the target entity to connect to.
joint_typeJointTypeThe joint type and its parameters.
collide_connectedboolWhether connected bodies can collide with each other (default: false).
break_forcef32The force that snaps the joint (Oct 2026; Unity’s breakForce), in the world’s units - mass times units a second squared, so a body of mass m hanging still pulls m * 981 in a project at the usual length_unit. 0, the default, never breaks. A broken joint is gone: the joint component is removed and the entity’s script hears on_joint_break.
break_torquef32The torque that snaps a hinge, likewise: what its limit (and its motor) carries - a door held open on its stop, a ragdoll limb bent past its swing. A free hinge carries none; the angle a fixed joint locks is not measured. 0 never breaks.

Audio source component — attaches a sound clip to an entity.

Scene JSON key: audio_source

FieldTypeDescription
clip_idStringAsset path for the audio clip (e.g. “sounds/jump.ogg”).
volumef32Volume (0.0 = silent, 1.0 = full).
pitchf32Pitch multiplier (1.0 = normal).
loopingboolWhether this clip loops.
startedboolSet once the source has been started, so autoplay fires exactly once. Runtime state; never serialized.
auto_playboolIf true, start playing automatically when the entity spawns.
bus_idOption<String>2.4 — mixer bus this source plays through. None routes to the default sfx bus. Custom buses can be created at runtime via audio_create_bus(name, parent, volume).
spatialboolHeard from where it is (Oct 2026, audio::spatial): louder near the listener - an audio_listener, or the active camera - and panned to its side, every frame as either moves. Off, it plays as before.
min_distancef32Full volume within this distance of the listener.
max_distancef32Silent from this distance.
pan_widthf32A source this far to one side is panned all the way to it.
occlusionf32The share of its volume each wall between it and the listener takes (audio::spatial; 0: walls change nothing). A wall is any non-sensor collider the line between them crosses.
mufflef32Its low-pass cutoff behind one wall, in hertz - each further wall halves it (0: no muffle).

GUI component — attaches a .gui layout document to an entity. At runtime the layout engine reads the referenced GuiDocument from the asset manager, resolves the widget tree into screen-space rectangles, and renders them on top of the game world.

Scene JSON key: gui

FieldTypeDescription
gui_idStringAsset ID of the .gui document (e.g. “ui/hud.gui”).
visibleboolWhether this GUI is currently visible.
variablesHashMap<String, serde_json::Value>Dynamic variables that scripts can bind to GUI nodes (e.g. { "score": 42, "health": 0.75 }).
focused_node_idOption<String>ID of the currently focused node (for keyboard navigation). None means no node has focus.
nav_focusboolRuntime state - not serialized. The focus was put there by navigation (navigate_ui), not by a click: only then may A / Enter / Space press it. A click that happens to focus a button must not let a later Space - a game’s jump, its “start wave” - press it again.
hovered_node_idOption<String>Runtime state: the button under the pointer, set once a frame by navigate_ui in every runtime - what draws a button’s hover_color.
pointer_downboolRuntime state: the pointer is down over hovered_node_id - its pressed_color.
hover_tf32Runtime state: how far into its hover hovered_node_id is, 0..1 - eased over [GUI_HOVER_SECONDS], so a button’s colour and hover_scale come on smoothly rather than in one frame.
pointer_uiOption<(f32, f32)>Runtime state: the pointer in UI pixels this frame, and a click on a TextInput waiting for apply_text_input to put the caret there (its x, whether shift was held) - track_gui_pointer sees the click, the text field’s update owns the document.
text_clickOption<(f32, bool)>
slider_dragOption<(String, f32)>Runtime state: a Slider the pointer pressed and still holds, and the value under the pointer this frame - track_gui_pointer sees the drag, navigate_ui (which owns the document) writes the value.

Material component — assigns a named material to an entity for rendering. When present on an entity with a SpriteComponent, the renderer will use the referenced material (shader + blend mode + uniforms) instead of the default sprite material. If the material is not found in the MaterialLibrary, the default is used.

Scene JSON key: material

FieldTypeDescription
material_idMaterialIdName of the material in the MaterialLibrary (e.g. “default_sprite”, “additive”).
blend_modeOption<BlendMode>How the sprite blends with what is under it: Alpha (the default), Additive (glows, fire, light - brightens, never darkens), Multiply, Screen or PremultipliedAlpha. Honoured on sprites and meshes, lit or Unlit, by ecs::wgpu_systems::sprite_blend; a material named "additive", "multiply" or "screen" means the same without the override. A shader-graph material blends as its .material says. Until Sep 2026 no renderer read this field, so it drew alpha-blended everywhere. Screen assumes colours at most 1.0 - in an HDR scene a brighter source darkens what is under it; use Additive there.
uniformsHashMap<String, UniformValue>Per-entity uniform overrides — merged on top of the material’s base uniforms.
normal_map_idOption<String>Optional normal map texture for per-pixel lighting (unit 1).
specularf32How strongly point and spot lights glint off the surface (Oct 2026): 0 is matt (the default), 1 a highlight as bright as the light. With a normal map the highlights follow its bumps, and the normal map’s alpha masks them - paint it dark where the sprite is cloth and light where it is metal.
shininessf32How tight the highlight is: 1 a broad sheen, 128 a pin-point glint.

A point light that illuminates a radial area around the entity’s position.

Scene JSON key: point_light

FieldTypeDescription
color[f32; 3]Light color (RGB).
intensityf32Light brightness multiplier.
radiusf32Maximum reach in world units.
fallofff32Falloff exponent (1.0 = linear, 2.0 = quadratic).
shadow_softnessf32Apparent emitter radius in world units used for soft shadow penumbra. 0.0 = a point emitter ⇒ hard shadows; larger values widen the penumbra where occluders cast shadows. Ignored when no LightOccluder is present.
heightf32How high the light sits above the plane, for normal maps only: the tangent of its elevation, so 0.5 (the default, and the only value before Sep 2026) lights a surface from about 27 degrees up. Lower is more grazing - a normal-mapped sphere shows a night side and a terminator instead of being lit all over, and relief stands out harder; higher lights from overhead. A flat surface takes the same light at any height (the response is relative to flat, so a scene without normal maps is unchanged), and a face turned to the light takes at most [MAX_NORMAL_RESPONSE] times as much. Clamped to at least 0.02.
casts_shadowsboolWhether occluders shadow this light. On by default (every light was shadowed before Sep 2026, and scenes were built that way); turn it off for a glow, a fill or a light that sits on the thing that would shadow it - a crystal atop a post, whose own post otherwise throws a wedge of dark down the floor below it. Godot’s shadow_enabled, and Unity’s shadow strength at zero.
cookieStringA texture the light shines through (Oct 2026) - a cookie, as Unity and Godot call it: a window’s grille, leaves, a stained-glass pattern. Its picture spans the light’s reach (radius times cookie_scale each way from the light), turns with the entity, and multiplies what the light gives: white passes, black stops, a colour tints, and alpha fades it. Outside the picture the light gives nothing, so a cookie is also the light’s shape. Empty is none.
cookie_scalef32How far the cookie’s picture reaches, as a share of radius from the light to each edge (1, the default, is the light’s whole reach).
turnf32The entity’s turn, which turns the cookie - filled from its world transform by gather_lights, never authored or saved.
light_masku32The light layers it lights (Oct 2026; Godot’s range_item_cull_mask, Unity’s target sorting layers): a sprite is lit by it when the sprite’s light_layers shares a bit with this. Every layer by default. Eight layers, the low eight bits.
subtractboolTakes light away instead of adding it (Oct 2026; Godot’s Light2D BLEND_MODE_SUB): a cursed lantern that casts darkness, a shadow cast by nothing. Drawn after the lights that add, and never below black. Its specular highlight subtracts too.

A light from far away - the sun, the moon: every ray parallel, the same strength everywhere, and every shadow thrown the same way. Godot’s DirectionalLight2D. Before Sep 2026 a sun was a point light placed thousands of units off screen (the showcase’s Clay), whose shadows still fanned out a little across the view, and whose shadows could not lengthen as it set. This is that, done by the engine: renderer::lighting::sun_as_point puts the light far out along its direction, beyond the view, so every consumer of point lights - the GPU pass, the occluder packing, light_at - handles it unchanged. A sun is swung by turning its entity (as a spot light is aimed), and set by lowering height: shadows lengthen as it does.

FieldTypeDescription
color[f32; 3]Light color (RGB).
intensityf32Light brightness multiplier.
directionf32Which way the light travels, in radians (0 is towards +x, -PI/2 straight down the screen, the default). The entity’s rotation is added.
heightf32How high the sun stands: the tangent of its elevation, 1 at 45 degrees. It lights normal maps as a point light’s height does, and it sets how long shadows are - an occluder’s shadow_length is the shadow it throws under a sun at height 1, and a sun at h throws it shadow_length / h long. Occluders with no shadow_length cast shadows to the edge of the view at any height. Clamped to at least 0.02.
softnessf32The sun’s apparent size, in radians: how soft the edges of its shadows are, widening with distance from the occluder. 0 is hard.
casts_shadowsboolWhether occluders shadow this light.
light_masku32The light layers it lights (Oct 2026; Godot’s range_item_cull_mask, Unity’s target sorting layers): a sprite is lit by the sun when the sprite’s light_layers shares a bit with this. Every layer by default. Eight layers, the low eight bits.

A directional cone light attached to an entity.

Scene JSON key: spot_light

FieldTypeDescription
color[f32; 3]Light color (RGB).
intensityf32Light brightness multiplier.
radiusf32Maximum reach in world units.
anglef32Cone half-angle in radians.
directionf32Direction angle in radians (0 = right, π/2 = up).
fallofff32Falloff exponent.
shadow_softnessf32Apparent emitter radius in world units used for soft shadow penumbra (see [PointLight2D::shadow_softness]).
heightf32How high the light sits above the plane, for normal maps - see [PointLight2D::height].
casts_shadowsboolWhether occluders shadow this light - see [PointLight2D::casts_shadows].
cookieStringA texture the light shines through (Oct 2026) - a cookie, as Unity and Godot call it: a window’s grille, leaves, a stained-glass pattern. Its picture spans the light’s reach (radius times cookie_scale each way from the light), turns with the spot’s aim, and multiplies what the light gives: white passes, black stops, a colour tints, and alpha fades it. Outside the picture the light gives nothing, so a cookie is also the light’s shape. Empty is none.
cookie_scalef32How far the cookie’s picture reaches, as a share of radius from the light to each edge (1, the default, is the light’s whole reach).
light_masku32The light layers it lights (Oct 2026; Godot’s range_item_cull_mask, Unity’s target sorting layers): a sprite is lit by it when the sprite’s light_layers shares a bit with this. Every layer by default. Eight layers, the low eight bits.
subtractboolTakes light away instead of adding it (Oct 2026; Godot’s Light2D BLEND_MODE_SUB): a cursed lantern that casts darkness, a shadow cast by nothing. Drawn after the lights that add, and never below black. Its specular highlight subtracts too.

Scene-wide ambient light. Attach to any entity (typically the camera or a dedicated “Lighting” entity). Only the first one found is used.

Scene JSON key: ambient_light

FieldTypeDescription
color[f32; 3]Ambient color (RGB).
intensityf32Ambient intensity (0 = total darkness, 1 = fully lit).

Ambient occlusion: the ambient light darkened near the outlines of LightOccluders, as it is where two surfaces meet - the soft darkening at the foot of a wall, in a corner, between two boxes set close. Put it on any one entity (the first found is used), beside the ambient_light. A point d from an occluder’s outline keeps 1 - strength * (1 - d/radius)^2 of the ambient light from that occluder, and nothing within radius of none; near two occluders the two multiply, so a corner or a narrow gap goes darker than one wall. A point inside an occluder takes none from that occluder - the top of a box is not darkened by its own sides. Only the ambient term is occluded: a lamp still lights a corner. Godot builds the same thing from its occluders’ distance field; the showcase’s Clay scene is built on it. renderer::lighting::ambient_occlusion_at is the rule, on the CPU and (transcribed) on the GPU.

FieldTypeDescription
radiusf32How far from an outline the darkening reaches, in world units.
strengthf32How dark it gets at the outline itself: 0 none, 1 black.

Global illumination (Oct 2026): light that bounces. The lit outlines of the occluders - the scene’s colour times the direct light on them - shine again, and every pixel gathers what reaches it round the occluders between, tinted by what it came off: sun through red glass onto a stone floor warms the walls beside it. Radiance cascades, on the GPU (renderer::backend::wgpu::gi), added to the light buffer before the composite. Godot and Unity have no 2D equivalent built in. One per scene, like ambient occlusion; Lighting.set_global_illumination sets it. light_at leaves bounced light out.

FieldTypeDescription
strengthf32How much of the bounce is added: 1 is what reaches each pixel.
scaleu32Frame pixels to a GI texel each way: the bounce is soft, so it is worked out small. 4 by default; 2 is finer and four times the work.
cascadesu32How many cascades - each reaches four times as far as the last. 0, the default, takes as many as cross the frame.

Toon lighting: the light that reaches each pixel is stepped into bands levels instead of falling off smoothly - cel shading. A normal-mapped sphere under one light shows a lit face, a mid band and a shadow band with crisp edges between them, as an anime or comic render does. Put it on any one entity (the first found is used). Applied in the lighting composite to the light buffer - ambient, lights and lightmaps summed - by its brightest channel, so a light’s colour is kept. light_at applies the same rule (renderer::lighting::toon_step). Each point goes to its nearest band, so light under half a band - an ambient below 1 / (2 * bands) - is black: hard cel shadows. For a lit shadow band, keep the ambient above that (0.17 for three bands).

FieldTypeDescription
bandsu32How many steps light takes between dark and full (at 1.0). Above 1.0 in an HDR scene the steps continue.
softnessf32How soft the edge between two bands is, as a fraction of a band: 0 is a hard line, 1 is smooth again.
ditherf32Dithered lighting: the size of a dither cell in world units, 0 for none. The softness of each band’s edge is then drawn as an ordered (4x4 Bayer) pattern of the two bands instead of a gradient - the look of a palette-limited retro game, whose light could only come in a few shades. Anchored to the world, so a cell the size of an art pixel stays on that pixel as the camera moves (and lands on the pixelate grid when both use it).

Baked lightmap: pre-computed light, added into the light buffer as a textured quad centred on the entity, before the scene is multiplied by it. A bright texel lights the scene under it exactly as a light would; black adds nothing. The quad is size world units (times the transform’s scale), rotated with the entity. Drawn by ecs::wgpu_systems::render_lightmaps_wgpu in every runtime’s lighting pass. Until Sep 2026 nothing drew it at all - this comment described a renderer that did not exist (test-lab BUGS #95).

Scene JSON key: lightmap

FieldTypeDescription
texture_idStringAsset path to the baked lightmap texture.
uv_rect[f32; 4]UV sub-region within the lightmap texture, [u_min, v_min, u_max, v_max].
intensityf32Intensity multiplier applied to the texture’s colour.
size[f32; 2]World size of the quad, before the transform’s scale.

Marks an entity as a light blocker. During the lighting pass each occluder contributes world-space edge segments that point and spot lights ray-test against — fragments whose line-of-sight to a light is crossed by any segment receive no (or, with soft shadows, reduced) contribution from that light, producing cast shadows / field-of-view wedges. Shape resolution priority (highest first): 1. polygon — an explicit local-space polygon (CCW, closed automatically). Each consecutive pair of points is one edge. 2. from_alpha — the silhouette of the sprite’s opaque pixels, traced from its texture (ecs::alpha_shape); its box until the pixels load. 3. from_sprite — derive an axis-aligned box from the entity’s SpriteComponent size (or its explicit size override). 4. size — a plain box size world units wide/tall. All shapes are transformed by the entity’s Transform (position + rotation + scale).

Scene JSON key: light_occluder

FieldTypeDescription
enabledboolWhen false the occluder is ignored (casts no shadow).
size[f32; 2]Full width/height of the blocking box in world units, before the entity’s Transform.scale is applied. Used when neither polygon nor from_sprite apply.
from_spriteboolDerive the blocking box from the entity’s SpriteComponent bounds instead of size.
polygonVec<[f32; 2]>Explicit local-space polygon outline (CCW). Overrides size / from_sprite when non-empty. Needs at least 3 points.
shadow_tint[f32; 3]RGB colour the shadow lets through when shadow_opacity is high. [0, 0, 0] (the default) produces a neutral shadow; a coloured tint like [1, 0, 0] makes a translucent occluder pass only the red channel, simulating coloured glass.
shadow_opacityf32How opaque the occluder is in [0, 1]. 1.0 (default) fully blocks light (tinted by shadow_tint); 0.0 casts no shadow at all; values in between produce translucent shadows. The light passing through equals mix([1,1,1], shadow_tint, shadow_opacity).
self_shadowboolWhether the occluder shadows its own inside - what Unity calls ShadowCaster2D.selfShadows. Off (the default since Sep 2026), a point inside the outline takes light as if the occluder were not there, while everything beyond it is shadowed as before - so a lit wall, pillar or crate that casts a shadow is lit itself, and its normal map shades it. Until then every occluder shadowed its inside: the ray from a light to a point inside crosses the outline on its way out, so a lit sprite under its own occluder drew black, and the advice was to draw it unlit, or - the showcase’s Crypt - to give a column an occluder of zero width. Turn it on for an occluder that stands for something solid whose inside should be dark, like a wall seen from above whose top the light cannot reach. A light inside the outline is still boxed in by it: points outside are shadowed, points inside are lit.
shadows_castersboolWhether its shadow falls on other casters - sprites whose entity has an occluder too. On (the default) is top-down lighting, where a wall shadows the crate beside it. Off is a side view (Sep 2026): a tower’s blocks stand in front of the wall behind them, so each casts onto the wall and not onto the blocks beside it - with it on, every stone was in its neighbours’ shadow and the tower drew black. The GPU knows a caster’s pixels from the normal pass; light_at has no pixels and treats every point as not a caster.
shadow_lengthf32How far the shadow reaches past the occluder, in world units; 0 (the default) is unbounded. The last quarter fades out. Lighting here is flat land: every occluder is infinitely tall, so a shadow runs on to the edge of the light. That is right for a wall and wrong for a bench under a far sun, whose shadow in a top-down view is a short patch - unbounded, the first pew in a nave shadows every pew behind it (the showcase’s Stained Glass met exactly that). Set it to the length the shadow would have: roughly the object’s height over the tangent of the light’s elevation. Up to about 8,000 units, in steps of four.
from_alphaboolCast the shape of the sprite (Oct 2026): the outline of its opaque pixels, traced from the texture - a tree’s crown and trunk, a fence’s pickets - rather than its box. Each island of the sprite is a loop; holes are left out.
alpha_cutofff32The alpha above which a pixel is part of the shape (0..1).
alpha_tolerancef32How far, in texels, the traced outline may stray from the pixels’ edge - larger is fewer corners. Every corner is an edge each nearby light spends from its pool of 448, so a loop is also held to 48.
tracedOption<crate::ecs::alpha_shape::TracedShape>The traced outline, kept until the sprite’s texture, region, size or anchor change (ecs::alpha_shape::trace_occluder_shapes). Never saved.

A moving surface (Oct 2026; Unity’s SurfaceEffector2D): whatever rests on this entity’s collider is carried along it at speed world units a second, along the entity’s own +x (turn the entity to aim it; negative runs the other way) - a conveyor belt, a moving walkway, a river’s surface, a treadmill. The collider itself does not move: rapier is told the contact’s surface slides (SolverContact::tangent_velocity), so friction carries what touches it, the way a real belt does, and a body on a slope of belt still slides if friction cannot hold it. Physics.set_conveyor_speed changes the speed.

FieldTypeDescription
speedf32

A collider in the shape of its sprite (Oct 2026; Unity’s PolygonCollider2D made from a sprite, Godot’s “create collision polygon”): the outline of the sprite’s opaque pixels - traced as light_occluder.from_alpha traces a shadow (ecs::alpha_shape) and scaled by the entity’s transform - is written into the entity’s own collider as a [ColliderShape::ConvexDecomposition], so physics, rays, shape queries, particle collisions, navigation and the F4 outlines all read one shape and none of them was touched. A collider is added if the entity has none, so friction and the rest can still be authored on one. Every island of the sprite is a loop of the shape (holes are left out). A body waits for its shape - nothing is simulated with a placeholder - and is given the new one when the sprite’s texture, region, size, anchor or scale change.

FieldTypeDescription
alpha_cutofff32The alpha above which a pixel is solid (0..1).
alpha_tolerancef32How far, in texels, the outline may stray from the pixels’ edge - larger is fewer, larger convex pieces.
keyu64What the written shape was traced from (texture, frame, cutoff, tolerance, scale); 0 until it is. Never saved.

2D velocity for simple kinematic motion (no physics engine needed).

Scene JSON key: velocity

FieldTypeDescription
xf32Horizontal velocity in pixels/sec.
yf32Vertical velocity in pixels/sec.
gravityf32Gravity acceleration in pixels/sec² (subtracted from y each frame).

Step 4 walking-skeleton: declares that this entity is processed by a batch script system. The system runs once per frame for all entities carrying a BatchScriptComponent whose (lang, path) matches, instead of invoking update(entity, dt) per entity. Walking-skeleton scope (Step 4.1/4.2): the batch’s read/write columns are fixed to Velocity2D (read x/y) → Transform.position (write x/y). system.query.read/write declared in the script header is parsed and validated against this fixed contract at load time (Slice 4.2). Slice 4B.1 adds a lang field so scripts can be Lua or JS — dispatch picks the matching runtime, but everything else (path, schema, mirror buffer layout) is language-agnostic.

Scene JSON key: batch_script

FieldTypeDescription
pathStringPath to the script module declaring an update(ctx, q) function. Relative to the project’s assets directory, same as [ScriptComponent::source_path].
langScriptLangScripting language. Defaults to Js for back-compat with prefabs authored before Slice 4B.1.

Axis-aligned bounding box that constrains entity position and bounces velocity.

Scene JSON key: bounce_area

FieldTypeDescription
min_xf32
min_yf32
max_xf32
max_yf32
dampingf32Energy preservation on bounce (0.0–1.0). 1.0 = perfectly elastic.

2D acceleration vector applied to Velocity2D each frame (vel += g * dt). Pixels/sec². For Earth-like downward gravity, use a negative y (the engine’s +y is up).

Scene JSON key: gravity

FieldTypeDescription
xf32
yf32

Despawn this entity once it has left the view. Every scrolling game needs this and, until Sep 2026, every one of them wrote it again in Lua: walk your own list of spawned things, compare each one against the camera, despawn the ones behind. Lifetime covers “this goes away after a while”; this covers “this goes away once nobody can see it”, which is the other half. Not [DespawnOutOfBounds], which is a fixed world-space box: in a game whose world scrolls, that box would have to move with the view, and there is nothing to move it. Use that one for a play area that stays put, and this one for anything the camera travels past.

Scene JSON key: despawn_offscreen

FieldTypeDescription
marginf32How far outside the camera’s view the entity may go before it is despawned, in world units. A margin gives a thing time to finish leaving before it disappears, and stops a wide sprite vanishing while half of it is still on screen.
behind_onlyboolOnly despawn once it is behind the camera’s travel - what a scrolling game wants, because whatever is ahead has not had its turn yet. “Behind” is judged per axis from the camera’s movement since the last frame: outside the view on the side the camera is moving away from. A camera that is not moving leaves nothing behind, so a still frame keeps what is waiting ahead of the view, and so does a camera that jumped further than the view in one frame (a cut, not travel).
seenboolWhether it has been inside the view (with its margin) yet - set by the despawn pass itself, never authored. The camera can only have passed something it has reached: a thing spawned off screen ahead has not had its turn, however the camera moves before it gets there. A runner restarting at the start of its track jumps the camera back, and without this the new run’s next chunk, spawned just off screen on that frame, read as “behind” and was deleted.

A trail: a ribbon left behind the entity as it moves - a sword’s swing, a comet’s tail, a light painted with the mouse. Unity’s TrailRenderer, Godot’s Line2D fed from a script. Every frame of play the entity’s world position is recorded once it has moved min_distance from the last point (ecs::trails::tick_trails, which every runtime runs right after resolving the hierarchy); a point lives lifetime seconds. Trail.set_emitting and Trail.clear steer it from a script. Long gaps between points - a fast swing - are drawn as curves, not chords. The ribbon runs from the entity’s position this frame back through the points, its width and colour going from width / color at the head to width_end / color_end at a point’s last moment - so with a color_end of alpha 0 or a width_end of 0 a point fades out rather than popping off the tail. The texture is stretched along the whole ribbon: u 0 at the head and 1 at the tail, v across. Drawn in the sprite pass (sorted by z_order with sprites, lit unless Unlit, blended by blend_mode). A trail on a child is the trail of where the child is - put one on the tip of a sword, not the hilt.

FieldTypeDescription
lifetimef32How long a point lives, in seconds - the length of the tail in time.
widthf32Width at the head, in world units.
width_endf32Width at a point’s last moment.
color[f32; 4]Colour (linear RGBA) at the head.
color_end[f32; 4]Colour at a point’s last moment.
min_distancef32How far the entity moves before a new point is recorded. Smaller is smoother and costs more points (at most [Trail::MAX_POINTS]).
texture_idStringTexture stretched along the ribbon; empty is plain colour.
z_orderi32
sort_layerOption<String>
emittingboolOff: no new points are recorded and the ones there age out.
blend_modecrate::renderer::material::BlendMode
pointsVec<TrailPoint>The recorded points, oldest first - kept by tick_trails, never authored.

One recorded point of a [Trail].

FieldTypeDescription
positionVec2
agef32Seconds since it was recorded.

On-screen controls for a touch screen: sticks and buttons that press keys. A phone has no keyboard, and the games here read one. A control presses a key - named directly ("Space") or through an input action ("jump" presses the first keyboard key bound to it) - so a keyboard game plays by touch unchanged. Put one on any entity in a scene that wants them; a scene without one (a menu) is tapped instead. The logic, and what each runtime has to do, is in input::touch_controls. json "touch_controls": { "controls": [ { "type": "stick", "anchor": "bottom_left", "offset": [160, 160], "up": "Up", "down": "Down", "left": "Left", "right": "Right" }, { "type": "button", "anchor": "bottom_right", "offset": [130, 130], "press": "jump", "label": "A" } ] }

Scene JSON key: touch_controls

FieldTypeDescription
showTouchControlsShowWhen the controls show (and take fingers): touch - once the screen has been touched, so a desktop never sees them; always - including on a desktop, where the mouse works them (for trying a layout); or never.
opacityf32Multiplies every control’s opacity, 0..1.
controlsVec<TouchControl>

A thumbstick pressing up to two of four directions (a diagonal is two).

FieldTypeDescription
anchorScreenAnchor
offset[f32; 2]From the anchored edge inward, in UI pixels (see input::touch_controls::place).
radiusf32
dead_zonef32How far from the centre, as a fraction of radius, before a direction presses.
upString
downString
leftString
rightString

A round button holding one key down while pressed.

FieldTypeDescription
anchorScreenAnchor
offset[f32; 2]
radiusf32
pressStringA key name or an input action.
labelStringDrawn on the button; empty for none.

Countdown timer that fires on_expire once remaining <= 0. update_lifetimes ticks remaining -= dt each frame and queues the configured action.

Scene JSON key: lifetime

FieldTypeDescription
remainingf32Remaining time in seconds. When this reaches zero, on_expire fires.
on_expireExpireAction

Per-entity acceleration applied to Velocity2D each frame (vel += a * dt). Distinct from Gravity2D in intent (gravity is the world’s constant downward pull; Acceleration2D is per-entity transient acceleration — engines, thrusters, drag forces, AI steering output). Composes additively with Gravity2D.

Scene JSON key: acceleration

FieldTypeDescription
xf32
yf32

2D velocity-proportional drag (vel *= (1 - drag * dt) per axis, clamped at zero). Integrated by behaviors::update_forces in every runtime since Sep 2026 - before that no native system read it, and it was only vocabulary for batch scripts, which still may read it (an entity with a running batch script is left to that script).

Scene JSON key: drag2d

FieldTypeDescription
xf32
yf32

2D force on a unit mass: vel += force * dt, integrated by behaviors::update_forces (Sep 2026; batch-script vocabulary only before that).

Scene JSON key: force2d

FieldTypeDescription
xf32
yf32

Ambient 2D wind: vel += wind * dt, integrated by behaviors::update_forces (Sep 2026). Distinct from Force2D so an entity can carry both - its own push and the weather’s.

Scene JSON key: wind2d

FieldTypeDescription
xf32
yf32

Instantaneous 2D impulse: added to the velocity once and then cleared, by behaviors::update_forces (Sep 2026). Set it again - from a graph’s SetCustomField, a script’s component write - for another kick.

Scene JSON key: impulse2d

FieldTypeDescription
xf32
yf32

Spin-up: AngularVelocity.omega += x * dt each frame, by behaviors::update_forces (Sep 2026). y is unused - the two fields keep the layout of the rest of the batch vocabulary.

Scene JSON key: torque2d

FieldTypeDescription
xf32
yf32

Clamps the magnitude of Velocity2D to at most value (pixels/sec). Run after gravity/acceleration integration, before bounce.

Scene JSON key: max_speed

FieldTypeDescription
valuef32

Sinusoidal positional motion. Each frame: position = center + (sin(2π·freq·t + phase) * amplitude) per axis. On the first tick (when center is None), the entity’s current Transform.position is captured as the oscillation center — this lets a designer drop an Oscillator on any sprite without having to set the center manually. Oscillator is self-contained — it sets Transform.position directly and does not touch Velocity2D. If you want a sprite that drifts AND oscillates, prefer combining a velocity-driven entity with a child Oscillator entity rather than stacking them on the same one.

Scene JSON key: oscillator

FieldTypeDescription
amplitude[f32; 2]Peak displacement per axis, in world units.
frequency[f32; 2]Cycles per second per axis.
phase[f32; 2]Phase offset per axis, in radians.
elapsedf32Accumulated time in seconds.
centerOption<[f32; 2]>Captured oscillation center. None means “capture from current Transform.position on first tick”.

Move toward target at up to max_speed (pixels/sec), stopping when within stop_distance. Writes directly to Transform.position — does not touch Velocity2D. For physics-style steering, use SeekTarget.

Scene JSON key: follow_target

FieldTypeDescription
targetEntityRef
offset[f32; 2]Constant offset added to the target’s position.
max_speedf32
stop_distancef32Don’t move when within this many world units of the target+offset.

Steer Velocity2D toward target by accelerating at accel (pixels/sec²). Velocity magnitude is clamped to max_speed. Requires a Velocity2D on the same entity (skipped silently otherwise). Arrival (Step 6d): when slowing_radius > 0 and the entity is inside that radius of the target, the per-frame speed cap is scaled linearly from max_speed (at the radius) down to 0 (at the target), so the seeker decelerates to a stop instead of orbiting.

Scene JSON key: seek_target

FieldTypeDescription
targetEntityRef
accelf32
max_speedf32
slowing_radiusf32Step 6d — arrival behavior. 0.0 (default) disables it and the component behaves exactly as in Step 2D. Positive values enable linear deceleration inside the radius.

Interpolate a single scalar property from from to to over duration. Designer-friendly defaults: linear ease, stop at end. See TweenTarget for the supported properties.

Scene JSON key: tween

FieldTypeDescription
targetTweenTarget
fromf32
tof32
durationf32
elapsedf32
easeEase
on_completeTweenComplete

References a named route and walks the entity along it at speed (pixels/sec). Routes are declared in the scene’s top-level paths block and collected into a [crate::ecs::path::Path2DLibrary], so a hundred patrol guards share one Vec<Vec2> rather than carrying a hundred copies. An unknown path_id is skipped silently — an asset typo leaves one guard standing still rather than crashing the level. The library was populated by nothing at all until Sep 2026: this component, the library and update_patrol_paths all existed with tests, and every runtime passed None, so patrol entities moved nowhere (BUGS #7).

Scene JSON key: patrol_path

FieldTypeDescription
path_idStringAsset ID into the Path2DLibrary.
speedf32
indexusizeIndex of the waypoint we are currently moving toward.
modePatrolMode
forwardbooltrue = walking 0 → N, false = walking N → 0 (only used by PingPong).

Shadow caster component — renders a shadow silhouette behind the entity’s sprite. Requires Transform and SpriteComponent on the same entity. The shadow is drawn using the sprite’s texture with the caster’s color tint, offset by offset * height in world units.

Scene JSON key: shadow_caster

FieldTypeDescription
enabledboolWhether this shadow caster is active.
color[f32; 4]Shadow tint color (RGBA). Typically dark and semi-transparent.
offset[f32; 2]Shadow offset direction in world units (before height multiplier).
heightf32Fake elevation — multiplies offset to push shadow further.
scale[f32; 2]Shadow scale relative to sprite size.
skewf32Horizontal shear for perspective-style shadows.

Vector path rendering component — defines 2D shapes via path commands. Paths are tessellated into triangles via lyon and drawn with ShapeBatch.

Scene JSON key: vector_path

FieldTypeDescription
commandsVec<PathCommand>Ordered list of path drawing commands.
fill_colorOption<[f32; 4]>Fill color (RGBA). If None, the path is not filled.
stroke_colorOption<[f32; 4]>Stroke/outline color (RGBA). If None, the path is not stroked.
stroke_widthf32Stroke width in world units.
z_orderi32Draw order — higher z_order draws on top.
closedboolWhether the path is closed (auto-close last sub-path).
sort_layerOption<String>Optional sort-layer name (see SortLayers).

Reads four named input actions (left/right/down/up) and produces a 2D axis vector each frame. The system [update_input_actions] writes current based on the global InputState; downstream consumers (scripts, motion components) read current.x / current.y. current is #[serde(skip)] — it is recomputed every frame and is not part of the serialized scene. Empty action names ("") are treated as “no binding” for that axis.

Scene JSON key: input_axis

FieldTypeDescription
x_negStringAction name driving negative X (e.g. "move_left").
x_posStringAction name driving positive X (e.g. "move_right").
y_negStringAction name driving negative Y (e.g. "move_down").
y_posStringAction name driving positive Y (e.g. "move_up").
scalef32Scale factor applied to the raw axis vector before assignment. Defaults to 1.0. Set to a movement speed (px/sec) and feed current directly into [Velocity2D] from a script for a one-line “WASD move” behavior.
normalizeboolWhen true and both axes are non-zero, normalize the (pre-scale) axis vector to unit length so diagonal movement isn’t √2 faster.
currentVec2Computed each frame by [update_input_actions]. Not serialized.
driveAxisDriveWhat this axis moves by itself, with no script: "velocity" sets the entity’s Velocity2D to current every frame, "acceleration" its Acceleration2D. "none" (the default) leaves current for a script to read, as it always did.

One-shot impulse triggered when a named action is just pressed (edge-triggered, not held). Writes directly into [Velocity2D] on the same entity. Each axis is optional — None leaves the existing component value alone, Some(v) replaces it. Canonical use: flappy-bird flap (action: "jump", impulse_y: Some(300.0)).

Scene JSON key: input_action_impulse

FieldTypeDescription
actionStringAction name to listen for (e.g. "jump").
impulse_xOption<f32>Replacement X velocity on trigger.
impulse_yOption<f32>Replacement Y velocity on trigger.

Attaches a behavior graph (.behavior asset) to an entity. The graph runs every frame in [crate::behavior::system::BehaviorGraphSystem], scheduled at Phase 2 (between input collection and native motion). See docs/step7-behavior-graph-spec.md §3.3.

Scene JSON key: behavior_graph

FieldTypeDescription
graph_pathStringPath to a .behavior asset, resolved against the project’s assets directory (same convention as [ScriptComponent::source_path]).
overridesstd::collections::HashMap<String, serde_json::Value>Per-instance overrides for nodes[].params, keyed by node id. Stored as JSON for scene serialization. Slice 7.2 ignores these; a later slice (7.4+) will resolve them into a typed ResolvedOverrides table at attach time so the runtime never touches serde_json::Value on the hot path.

Spatial trigger volume that emits enter/exit/staying events into the sibling [TriggerOverlaps] component each frame. Pure data — the [update_trigger_areas] system performs all overlap detection. Other entities are considered “candidates” if they have a Transform and at least one tag from match_tags (or match_tags is empty, in which case every other entity with a Transform is tested). The match_tags field is a deliberately simple alternative to a full physics layer/mask scheme — it composes with the existing [Tags] component without introducing a new vocabulary.

Scene JSON key: trigger_area

FieldTypeDescription
shapeTriggerShape
match_tagsVec<String>Only entities carrying at least one of these tags are considered for overlap. Empty list = match every entity with a Transform.

Per-frame overlap state written by [update_trigger_areas] onto every entity that carries a [TriggerArea2D]. Lists are cleared and repopulated each frame; entity ids are hecs::Entity::to_bits().get() values for stable cross-frame identification from scripts. entered and exited are edge-triggered (compared to last frame’s staying set); staying is the full overlap set this frame.

FieldTypeDescription
enteredVec<u64>
stayingVec<u64>
exitedVec<u64>

Authoritative combat HP for the native damage systems. Scripts read a mirrored copy via the Health custom component (written by mirror_health_to_custom) and never write this directly.

Scene JSON key: health

FieldTypeDescription
currentf32
maxf32
invuln_timef32Seconds of invulnerability granted after a hit (i-frames). 0.0 = hittable every frame a hitbox overlaps.
invuln_timerf32Runtime i-frame countdown; not serialized.

An offensive overlap hitbox. Pairs with a sibling [TriggerArea2D] (shape + match_tags target filter). Each frame apply_hitbox_damage damages every overlapping entity that carries a [Health], respecting this hitbox’s per-target retrigger cooldown and the victim’s i-frames.

Scene JSON key: hitbox

FieldTypeDescription
damagef32
knockbackf32Positional knockback distance applied to the victim, directed away from this hitbox’s entity. 0.0 = none.
activeboolWhen false the hitbox deals no damage (toggle for timed swings).
retriggerf32Seconds before the same victim can be hit again by this hitbox. 0.0 = every frame (bounded only by the victim’s i-frames).
despawn_on_hitboolDespawn this hitbox’s entity right after it lands a hit. Used for projectiles (arrows/bullets) that die on impact. Such a hitbox damages exactly one victim - the nearest it can hurt - even when it overlaps several on the frame it lands.
cooldownsHashMap<u64, f32>Runtime per-victim cooldown timers (victim to_bits -> seconds left). Not serialized.

Marker: entities carrying this are despawned by despawn_dead once their [Health::current] reaches 0. Entities without it (e.g. the player) persist at 0 HP so gameplay scripts can react to death.

Scene JSON key: despawn_on_death


Static circular blocker. resolve_obstacle_collisions pushes every mover out to radius from this entity’s position, giving sliding wall/pillar collision without a physics backend. A mover is anything with a [Health], or with a [Velocity2D] and no RigidBody2D (rapier owns those); a despawn_on_hit projectile is not pushed but consumed.

Scene JSON key: obstacle

FieldTypeDescription
radiusf32Keep-out radius: movers are pushed so their center sits at least this far from the obstacle’s center. Author it as the visual radius plus a margin for the mover’s own size.

Marks an entity spawned in bulk by a system, rather than authored or individually meaningful. Three things skip it, for the same reason — nobody addresses it one at a time: 1. The per-frame script mirror. The browser-script hosts copy every entity’s transform into JS each frame so a script could call get_position(e). Marked entities are left out. 2. The editor’s Scene panel. The panel renders one DOM row per entity with several event handlers, and it does not virtualise — 16,000 boids meant ~16,000 rows and roughly 68 ms a frame of layout, against 24 ms of actual engine work. A list that long is useless to a human anyway. 3. Scene snapshots. push_scene / pop_scene and the start of a play session serialize the whole world, and that cost is a hitch mid-game rather than something a save dialog pays. Marked entities are left out by [crate::scene::snapshot_scene_v2]: they belong to a spawner that recreates them, so writing 16,000 boids into a snapshot preserves state nobody addressed individually. Measured at 16,000: 40.05 ms -> 0.35 ms. A real save still keeps every entity — dropping one there would be data loss. Put it on flocks, projectiles, debris — anything a system creates by the thousand. Simulation and rendering are completely unaffected. The trade is exact: a marked entity’s slot in the script mirror is never filled, so get_position(e) reads the zeroed default rather than its real transform; it cannot be selected in the editor; and it does not survive a scene push. If you need to read, move or inspect an entity individually — or to have it come back after a pop_scene — do not mark it. (Named NoScriptSync when first added; renamed once it also governed editor visibility, since the old name described only a third of what it does.)

FieldTypeDescription
radiusf32Soft mutual separation: entities carrying this gently push apart from each other when overlapping (crowd spacing). apply_separation only considers other Separation entities, so the player / props are unaffected unless they opt in. Personal-space radius; a pair pushes apart while closer than the sum of their radii.

A floating health bar drawn above an entity that also carries a [Health]. Rendered by sync_health_bars, which maintains two runtime-only child sprite entities per owner (a dark background and a colored fill that shrinks with HP). Purely cosmetic: authored on the owner; the bar sprites themselves are transient and never serialized.

Scene JSON key: health_bar

FieldTypeDescription
widthf32Full-width of the bar in world units.
heightf32Bar height in world units.
y_offsetf32Vertical offset above the owner’s origin.
hide_when_fullboolHide the bar while HP is full (typical for enemies).

Runtime-only back-link from a health-bar sprite to its owner. Managed entirely by sync_health_bars; not serialized and not authorable.

FieldTypeDescription
ownerhecs::Entity
is_fillbooltrue for the colored fill quad, false for the dark backing.

Runtime-only back-link from a floating name-tag text entity to its owner. Managed by sync_name_tags; not serialized and not authorable. The label string is read from the owner’s CustomComponents["NameTag"]["text"], so scripts author it with the existing set_custom API (empty = no tag).

FieldTypeDescription
ownerhecs::Entity

Wraps Transform.position around a rectangular world-space box (Asteroids-style screen wrap). When the entity crosses any edge the position is teleported to the opposite edge — useful for looping playfields, parallax tiles, and bullet-hell screens. Pure: only mutates Transform. The wrap is unconditional — designers usually pair this with a Velocity2D for motion. The box is inclusive on min and exclusive on max so a position exactly at max snaps to min.

Scene JSON key: wrap_bounds

FieldTypeDescription
min[f32; 2]
max[f32; 2]

Clamps Transform.position inside a rectangular world-space box. Optionally zeros the corresponding Velocity2D axis on contact so a player held against a wall doesn’t accumulate undelivered velocity. Pure: mutates Transform and (when zero_velocity_on_hit is true) Velocity2D. Run AFTER position integration so this frame’s motion is what gets clamped.

Scene JSON key: clamp_bounds

FieldTypeDescription
min[f32; 2]
max[f32; 2]
zero_velocity_on_hitboolZero the matching Velocity2D axis when the entity is pushed back inside the box. Has no effect on entities without a Velocity2D. Defaults to true — the common “keep player on screen” use case.

Marks an entity for despawn when its Transform.position leaves a rectangular world-space box (with an optional outward margin). Returned by update_despawn_out_of_bounds as a list of hecs::Entity for the caller to destroy — same pattern as [update_lifetimes]. Canonical use: bullets / particles that exit the play area.

Scene JSON key: despawn_out_of_bounds

FieldTypeDescription
min[f32; 2]
max[f32; 2]
marginf32Extra outward slack before despawn fires. 0.0 despawns the instant the entity crosses the box edge; positive values let it travel a bit further before vanishing (useful for sprites whose visual extends past their pivot).

Rotates Transform.rotation to face the entity’s current Velocity2D (radians, atan2 convention). Skips frames where the velocity magnitude is below min_speed so a stationary entity doesn’t snap to an arbitrary heading. offset_radians is added to the computed angle — set to π/2 when the sprite art points “up” instead of “right”. Pure: mutates only Transform.rotation. Run AFTER any system that writes Velocity2D (gravity, accel, seek, max_speed, input).

Scene JSON key: face_velocity

FieldTypeDescription
offset_radiansf32Radians added to the computed atan2(vy, vx) heading. Default 0.0 assumes sprite art faces +X.
min_speedf32Skip rotation when `
turn_ratef32How fast it turns to its heading, radians per second; 0 (the default) turns at once, as it always did. A car rounding a corner wants 6 - 10.

Constant angular velocity (radians/sec) applied to Transform.rotation. Independent of Velocity2D — use this for pure spinning motion (collectibles, gears, propeller blades). Pure: mutates only Transform.rotation.

Scene JSON key: angular_velocity

FieldTypeDescription
omegaf32Spin rate in radians per second. Positive = counter-clockwise in the engine’s Y-up convention.

Periodically toggles SpriteComponent.color alpha between 0.0 and the entity’s pre-flicker alpha at frequency toggles per second, for a fixed duration. Auto-removes itself when elapsed >= duration and restores the original alpha. Canonical use: post-hit “iframe” damage flash on a player or enemy. Requires a SpriteComponent on the same entity (skipped silently otherwise).

Scene JSON key: flicker

FieldTypeDescription
frequencyf32Toggles per second (e.g. 10.0 = 5 on, 5 off per second).
durationf32Total flicker time in seconds. Component is removed after this.
elapsedf32Time elapsed inside this flicker. Initialise to 0.0.
original_alphaOption<f32>Captured alpha at flicker start. None means “capture from SpriteComponent.color[3] on first tick” — same self-priming pattern as Oscillator.center.

Transient additive jitter offset applied to Transform.position for duration seconds. Intensity decays linearly from intensity at start to 0 at end. Auto-removes itself when expired and restores the entity to its pre-shake position. Canonical use: hit-reaction camera shake or impact-frame entity shake. The shake is sampled at frequency Hz with linear interpolation between samples so the motion stays jittery rather than a smooth sine. Pure: mutates only Transform.position, and only by the jitter: each tick removes last tick’s offset and adds a new one, so whatever else moves the entity keeps moving it. When the shake ends the offset is removed.

Scene JSON key: shake

FieldTypeDescription
intensityf32Peak displacement magnitude in world units at start.
frequencyf32New random offset samples per second.
durationf32Total shake duration in seconds.
elapsedf32Time elapsed inside this shake. Initialise to 0.0.
offsetOption<[f32; 2]>Offset applied on the last tick, removed before the next is applied. Runtime state; None until the first tick. Replaces a captured origin the entity was pinned to every tick, which froze anything moving - a knocked-back enemy, a walking player - for the length of the shake and then snapped it back to where the shake began. Scenes that serialized origin still load; it is ignored.
seedOption<u32>Internal PRNG state seeded on first tick — kept on the component so each entity has its own deterministic stream.

How a sprite’s drawn area is filled with its source texture. Border / tile-size values are expressed in the same units as SpriteComponent::size (world units). A nine-slice border means the same number of texture pixels: a 16-unit border draws the texture’s outer 16 pixels 16 units wide, however large the sprite. So the corners keep their shape and only the edges and centre stretch. The texture’s size is looked up once it is loaded (NineSlice::source_px, filled by resolve_sprite_regions); until then the sprite draws stretched. Until Sep 2026 the UV slice was border / size - the drawn size - which makes every cell sample exactly the part of the texture a plain stretch would. NineSlice drew identically to Stretch (test-lab BUGS #96).

VariantFields
Stretch—
NineSliceleft: f32, right: f32, top: f32, bottom: f32, source_px: Option<[f32; 2]>
Tileamount: f32, from: FillFrom
Radialamount: f32, start: f32, clockwise: bool

Which side a [SpriteFill::Bar] fills from.

VariantFields
Left—
Right—
Bottom—
Top—

Which scripting language a script uses.

VariantFields
Lua—
JavaScript—

Rigid body type for physics simulation.

VariantFields
Dynamic—
Static—
Kinematic—

Shape type for a 2D collider.

VariantFields
Boxvertices: Vec<[f32; 2]>, loops: Vec<u32>

Joint type for connecting two physics bodies.

VariantFields
Revoluteanchor: [f32; 2], anchor_b: [f32; 2], limits: Option<[f32; 2]>, motor_speed: f32, motor_max_torque: f32
Fixedanchor_a: [f32; 2], anchor_b: [f32; 2]
Prismaticanchor: [f32; 2], anchor_b: [f32; 2], axis: [f32; 2], limits: Option<[f32; 2]>, motor_speed: f32, motor_max_force: f32
Ropeanchor_a: [f32; 2], anchor_b: [f32; 2], max_distance: f32
Springanchor_a: [f32; 2], anchor_b: [f32; 2], rest_length: f32, stiffness: f32, damping: f32
Wheelanchor: [f32; 2], anchor_b: [f32; 2], axis: [f32; 2], frequency: f32, damping_ratio: f32, limits: Option<[f32; 2]>, motor_speed: f32, motor_max_torque: f32
Motorlinear_offset: [f32; 2], angular_offset: f32, max_force: f32, max_torque: f32, frequency: f32, damping_ratio: f32
Frictionanchor_a: [f32; 2], anchor_b: [f32; 2], max_force: f32, max_torque: f32
Targetanchor: [f32; 2], target: [f32; 2], frequency: f32, damping_ratio: f32, max_force: f32

Which scripting language a [BatchScriptComponent] is written in. Scenes default to Js so existing prefabs from Slice 4.1/4.2 (which were JS-only) continue to deserialize without a lang field.

VariantFields
Js—
Lua—

What to do when a Lifetime’s remaining reaches zero.

VariantFields
Despawn—

When [TouchControls] show.

VariantFields
Touch—
Always—
Never—

One on-screen control.

VariantFields

The screen edge or corner a touch control is placed from.

VariantFields
TopLeft—
Top—
TopRight—
Left—
Center—
Right—
BottomLeft—
Bottom—
BottomRight—

A reference to another entity that is stable across save/load. Raw hecs::Entity is fine at runtime but useless in scene files — the generation/index combo is regenerated each load. EntityRef lets a component point at “the player”, “the entity named X”, or “myself” in a way that survives serialization. Variants are added incrementally as the codebase needs them. Today Runtime, Named, Player and SelfEntity are wired into the resolver; SceneUuid is a placeholder for upcoming editor work and resolves to None.

VariantFields
Player—
SelfEntity—

What scalar property a Tween drives. Typed enum (not a dynamic TweenValue blob) so the runtime never has to do a per-tick type check — the system matches once per entity per frame. Vector tweens (Position2D, SpriteColor) are intentionally omitted in the first cut; compose two scalar tweens, or add them later when a real use case forces the shape.

VariantFields
PositionX—
PositionY—
Rotation—
ScaleX—
ScaleY—
Opacity—

Easing curve. apply(t) maps t ∈ [0, 1] → eased [0, 1].

VariantFields
Linear—
EaseInQuad—
EaseOutQuad—
EaseInOutQuad—

What to do once a Tween reaches its end.

VariantFields
Stop—
Loop—
PingPong—

How a PatrolPathRef traverses its waypoints once it hits the end.

VariantFields
Loop—
PingPong—
Once—

A single path command — moveto, lineto, bezier curves, or close.

VariantFields
MoveToc1x: f32, c1y: f32, c2x: f32, c2y: f32, x: f32, y: f32
Close—

What an [InputAxis2D] drives on its own entity.

VariantFields
None—
Velocity—
Acceleration—

Shape of a [TriggerArea2D].

VariantFields
Aabb—