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.
Summary
Section titled “Summary”| Component | Scene Key | Description |
|---|---|---|
Transform | transform | 2D 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. |
Bone | bone | Marks an entity as a bone within a skeleton. |
SkeletonRoot | skeleton_root | Marks 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. |
SpriteComponent | sprite | Sprite rendering component. |
ScriptComponent | script | Script component — attaches a script to an entity. |
Name | — | Human-readable name for an entity. |
TextComponent | text | Text 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. |
Tags | tags | Tags for entity grouping — scripts can query entities by tag. |
PrefabInstance | prefab_instance | Marks an entity as an instance of a named prefab template. |
CameraFollow | camera_follow | Camera follow component — makes the camera track this entity, or frame a whole set of them. |
Camera2DComponent | camera | Camera component — marks an entity as a game camera. |
RenderLayers | render_layers | 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). |
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. |
SpriteMask | sprite_mask | A mask: what entities with masked naming this one are clipped to - a portal, a window, a frame (renderer::mask). |
Masked | masked | 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. |
RigidBody2D | rigid_body | Rigid body component — attaches a physics body to an entity. |
Collider2D | collider | Collider 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. |
Joint2D | joint | Joint component — connects this entity’s body to another entity’s body. |
AudioSource | audio_source | Audio source component — attaches a sound clip to an entity. |
GuiComponent | gui | GUI component — attaches a `. |
MaterialComponent | material | Material component — assigns a named material to an entity for rendering. |
PointLight2D | point_light | A 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. |
SpotLight2D | spot_light | A directional cone light attached to an entity. |
AmbientLight | ambient_light | Scene-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. |
LightmapComponent | lightmap | 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. |
LightOccluder | light_occluder | Marks 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. |
Velocity2D | velocity | 2D velocity for simple kinematic motion (no physics engine needed). |
BatchScriptComponent | batch_script | Step 4 walking-skeleton: declares that this entity is processed by a batch script system. |
BounceArea | bounce_area | Axis-aligned bounding box that constrains entity position and bounces velocity. |
Gravity2D | gravity | 2D acceleration vector applied to Velocity2D each frame (vel += g * dt). |
DespawnOffscreen | despawn_offscreen | Despawn 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]. |
TouchControls | touch_controls | On-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. |
Lifetime | lifetime | Countdown timer that fires on_expire once remaining <= 0. |
Acceleration2D | acceleration | Per-entity acceleration applied to Velocity2D each frame (vel += a * dt). |
Drag2D | drag2d | 2D velocity-proportional drag (vel *= (1 - drag * dt) per axis, clamped at zero). |
Force2D | force2d | 2D force on a unit mass: vel += force * dt, integrated by behaviors::update_forces (Sep 2026; batch-script vocabulary only before that). |
Wind2D | wind2d | Ambient 2D wind: vel += wind * dt, integrated by behaviors::update_forces (Sep 2026). |
Impulse2D | impulse2d | Instantaneous 2D impulse: added to the velocity once and then cleared, by behaviors::update_forces (Sep 2026). |
Torque2D | torque2d | Spin-up: `AngularVelocity. |
MaxSpeed | max_speed | Clamps the magnitude of Velocity2D to at most value (pixels/sec). |
Oscillator | oscillator | Sinusoidal positional motion. |
FollowTarget | follow_target | Move toward target at up to max_speed (pixels/sec), stopping when within stop_distance. |
SeekTarget | seek_target | Steer Velocity2D toward target by accelerating at accel (pixels/sec²). |
Tween | tween | Interpolate a single scalar property from from to to over duration. |
PatrolPathRef | patrol_path | References a named route and walks the entity along it at speed (pixels/sec). |
ShadowCaster | shadow_caster | Shadow caster component — renders a shadow silhouette behind the entity’s sprite. |
VectorPath | vector_path | Vector path rendering component — defines 2D shapes via path commands. |
InputAxis2D | input_axis | Reads four named input actions (left/right/down/up) and produces a 2D axis vector each frame. |
InputActionImpulse | input_action_impulse | One-shot impulse triggered when a named action is just pressed (edge-triggered, not held). |
BehaviorGraphComponent | behavior_graph | Attaches a behavior graph (`. |
TriggerArea2D | trigger_area | Spatial 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]. |
Health | health | Authoritative combat HP for the native damage systems. |
Hitbox | hitbox | An offensive overlap hitbox. |
DespawnOnDeath | despawn_on_death | Marker: entities carrying this are despawned by despawn_dead once their [Health::current] reaches 0. |
Obstacle | obstacle | Static circular blocker. |
BulkEntity | — | Marks an entity spawned in bulk by a system, rather than authored or individually meaningful. |
HealthBar | health_bar | A 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. |
WrapBounds | wrap_bounds | Wraps `Transform. |
ClampBounds | clamp_bounds | Clamps `Transform. |
DespawnOutOfBounds | despawn_out_of_bounds | Marks an entity for despawn when its `Transform. |
FaceVelocity | face_velocity | Rotates `Transform. |
AngularVelocity | angular_velocity | Constant angular velocity (radians/sec) applied to `Transform. |
Flicker | flicker | Periodically toggles `SpriteComponent. |
Shake | shake | Transient additive jitter offset applied to `Transform. |
Transform
Section titled “Transform”2D transform: position, rotation, and scale.
Scene JSON key: transform
| Field | Type | Description |
|---|---|---|
position | Vec2 | |
rotation | f32 | Rotation in radians. |
scale | Vec2 |
WorldTransform
Section titled “WorldTransform”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.
| Field | Type | Description |
|---|---|---|
position | Vec2 | |
rotation | f32 | Rotation in radians. |
scale | Vec2 | |
shear | f32 | Shear between the scale and the rotation (see the type’s docs). |
Parent
Section titled “Parent”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
| Field | Type | Description |
|---|---|---|
name | String | |
length | f32 |
SkeletonRoot
Section titled “SkeletonRoot”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
| Field | Type | Description |
|---|---|---|
name | String |
IkTwoBone
Section titled “IkTwoBone”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.
| Field | Type | Description |
|---|---|---|
target | [f32; 2] | |
flip_bend | bool | |
weight | f32 | How much of the solve to apply, 0..1. |
ground | Option<IkGround> | Plant the animated foot on the ground under it (see [IkGround]). |
IkChain
Section titled “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). 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.
| Field | Type | Description |
|---|---|---|
target | [f32; 2] | |
target_name | String | |
bones | u32 | |
iterations | u32 | |
tolerance | f32 | Close enough, in world units. |
max_bend | f32 | |
weight | f32 | |
state | Vec<(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. |
IkGround
Section titled “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.
| Field | Type | Description |
|---|---|---|
floor | f32 | The 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. |
reach | f32 | How far above and below that floor to look for ground. |
mask | u16 | The collision layers that count as ground (a collider’s collision_membership). |
IkLookAt
Section titled “IkLookAt”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).
| Field | Type | Description |
|---|---|---|
target | [f32; 2] | |
rotation_offset | f32 |
MeshVertex
Section titled “MeshVertex”One vertex of a [Mesh2D].
| Field | Type | Description |
|---|---|---|
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. |
q | f32 | Perspective 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. |
Mesh2D
Section titled “Mesh2D”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.
| Field | Type | Description |
|---|---|---|
texture_id | String | |
region | Option<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_rect | Option<[f32; 4]> | The part of the texture the vertices’ uv span, [u0, v0, u1, v1]; the whole texture when absent. |
region_resolved | bool | |
vertices | Vec<MeshVertex> | |
indices | Vec<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_order | i32 | |
sort_layer | Option<String> |
SpriteComponent
Section titled “SpriteComponent”Sprite rendering component.
Scene JSON key: sprite
| Field | Type | Description |
|---|---|---|
texture_id | TextureId | Asset 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_order | i32 | Draw 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. |
size | Option<[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_rect | Option<[f32; 4]> | UV rectangle for sprite atlas regions: [u_min, v_min, u_max, v_max]. Defaults to full texture [0, 0, 1, 1]. |
region | Option<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_resolved | bool | Whether 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. |
fill | Option<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_layer | Option<String> | Optional sort-layer name. When set, the active .sortlayers asset’s base_z is added to z_order at render time. |
y_sort | bool | Sort 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. |
corners | Option<[[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_px | Option<[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_for | TextureId | Which texture texture_px was measured from, so a texture swap (set_sprite_region, an animation frame) is measured again. |
ScriptComponent
Section titled “ScriptComponent”Script component — attaches a script to an entity.
Scene JSON key: script
| Field | Type | Description |
|---|---|---|
language | ScriptLanguage | |
source_path | String | Path to the script source file (relative to project assets). |
cull_distance | Option<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.
TextComponent
Section titled “TextComponent”Text rendering component — renders text using a bitmap font.
Scene JSON key: text
| Field | Type | Description |
|---|---|---|
text | String | The text string to display. |
font_id | String | Path 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_scale | f32 | Font scale (1.0 = native pixel size). |
color | [f32; 4] | RGBA tint color. |
z_order | i32 | Draw order — higher z_order draws on top. |
sort_layer | Option<String> | Optional sort-layer name (see SortLayers). |
wrap_width | f32 | Wrap 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. |
align | TextAlign | Horizontal 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. |
rich | bool | Read 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_speed | f32 | A typewriter: characters a second, waiting at each [pause], from whenever text last changed. 0 (the default) shows it all at once. |
reveal | Option<TextReveal> | The typewriter’s clock, kept by rich_text::tick_reveals; None until it first ticks, which shows the whole text. |
outline | Option<TextOutline> | An outline round every glyph ([TextOutline], Oct 2026). |
shadow | Option<TextShadow> | A shadow under the text, offset and softened ([TextShadow]). |
text_key | String | A 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. |
localized | u64 | The locale::stamp the text was last written for. Never saved. |
TextOutline
Section titled “TextOutline”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.
| Field | Type | Description |
|---|---|---|
width | f32 | How far past the glyph’s edge, in world units. |
color | [f32; 4] | Linear RGBA. |
TextShadow
Section titled “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.
| Field | Type | Description |
|---|---|---|
offset | [f32; 2] | |
color | [f32; 4] | |
softness | f32 |
TextReveal
Section titled “TextReveal”Where a typewriter is (TextComponent::reveal_speed): seconds since the text with this hash was set.
| Field | Type | Description |
|---|---|---|
key | u64 | |
elapsed | f32 |
CustomComponents
Section titled “CustomComponents”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>.
PrefabInstance
Section titled “PrefabInstance”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
| Field | Type | Description |
|---|---|---|
prefab_id | String | Name of the prefab template to instantiate. Looked up in the runtime’s services.prefabs map (populated from assets/prefabs/*.prefab). |
overrides | HashMap<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. |
CameraFollow
Section titled “CameraFollow”Camera follow component — makes the camera track this entity, or frame a whole set of them.
Scene JSON key: camera_follow
| Field | Type | Description |
|---|---|---|
smoothing | f32 | How 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. |
offset | Vec2 | Camera offset from the entity position (in world units). |
bounds | Option<[f32; 4]> | Optional bounds: [min_x, min_y, max_x, max_y]. Camera won’t move beyond these. |
frame_tag | Option<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_padding | f32 | World units of margin around the framed set, so the things being framed are not flush against the edge of the screen. |
zoom_range | Option<[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_ahead | f32 | Look 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_max | f32 | The furthest the look-ahead may lead, in world units. |
look_ahead_smoothing | f32 | How 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. |
Camera2DComponent
Section titled “Camera2DComponent”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
| Field | Type | Description |
|---|---|---|
zoom | f32 | Zoom level (1.0 = default). Higher values zoom in. |
is_active | bool | Whether this camera is the currently active (rendering) camera. |
viewport_width | f32 | Viewport width hint (overridden at runtime by actual window size). |
viewport_height | f32 | Viewport height hint (overridden at runtime by actual window size). |
stretch | bool | Stretch 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_size | f32 | Draw 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_scale | bool | With 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. |
target | String | Draw 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_mask | u32 | Which 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. |
priority | i32 | Which 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). |
blend | f32 | Seconds 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. |
confine | Vec<[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. |
RenderLayers
Section titled “RenderLayers”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
| Field | Type | Description |
|---|---|---|
layers | u32 |
ColorLut
Section titled “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. Applied in the output pass after the tonemap, mixed in by strength. The first one in the world is used.
| Field | Type | Description |
|---|---|---|
texture_id | String | The strip’s texture. |
strength | f32 | 0 is no grade, 1 the LUT’s. |
LightLayers
Section titled “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. 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.
| Field | Type | Description |
|---|---|---|
layers | u32 | The layers, as bits: layer 1 is bit 0 (1), layer 2 bit 1 (2), and a sprite on both is 3. |
SpriteMask
Section titled “SpriteMask”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
| Field | Type | Description |
|---|---|---|
shape | crate::renderer::mask::MaskShape | |
feather | f32 | The soft edge, in the shape’s own units; 0 is hard. |
Masked
Section titled “Masked”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
| Field | Type | Description |
|---|---|---|
by | String | |
outside | bool |
RigidBody2D
Section titled “RigidBody2D”Rigid body component — attaches a physics body to an entity.
Scene JSON key: rigid_body
| Field | Type | Description |
|---|---|---|
body_type | BodyType | |
mass | f32 | |
gravity_scale | f32 | |
linear_damping | f32 | |
angular_damping | f32 | |
fixed_rotation | bool | |
ccd | bool | Sweep 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_sleep | bool | Whether 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. |
Collider2D
Section titled “Collider2D”Collider component — defines the collision shape for an entity’s body.
Scene JSON key: collider
| Field | Type | Description |
|---|---|---|
shape | ColliderShape | |
offset | [f32; 2] | |
is_sensor | bool | |
friction | f32 | |
restitution | f32 | |
density | f32 | |
collision_membership | u16 | Collision layer membership bitmask — which layers this collider belongs to. |
collision_filter | u16 | Collision layer filter bitmask — which layers this collider can collide with. |
one_way | bool | A 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. |
CharacterController2D
Section titled “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. 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.
| Field | Type | Description |
|---|---|---|
velocity | [f32; 2] | The movement asked for this frame, in units per second. |
skin | f32 | The gap kept between the character and what it touches, in units. |
max_slope_deg | f32 | The steepest slope it walks up, in degrees; anything steeper is a wall. |
min_slide_deg | f32 | Slopes at least this steep slide it down, in degrees. |
snap_to_ground | f32 | How 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_height | f32 | The tallest ledge it steps up without jumping, in units. 0 turns it off. |
step_width | f32 | The least room a ledge must have on top to be stepped onto, in units. |
push_mass | f32 | Pushes dynamic bodies it walks into as if it weighed this much. 0 leaves them alone (they still block it). |
drop_through | bool | Falls through one-way platforms while set. |
ride_platforms | bool | Goes down with a kinematic platform it stands on. (Being carried sideways and upward is rapier’s own doing and always happens.) |
grounded | bool | Standing on something. |
ground_normal | [f32; 2] | The normal of what it stands on (0, 1 on flat ground). |
ground_entity | Option<u64> | The entity it stands on. |
wall | i32 | -1 against a wall on its left, 1 on its right, 0 for none. |
ceiling | bool | Bumped its head this frame. |
sliding | bool | Sliding 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. |
touching | Vec<u64> | Every entity whose collider it overlaps or touches after moving, sensors included - pickups, hazards, doors. |
TilemapOccluder
Section titled “TilemapOccluder”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.
| Field | Type | Description |
|---|---|---|
enabled | bool | When false the map casts no shadow. |
layers | Vec<String> | The layers that cast shadows, by name. |
shadow_tint | [f32; 3] | As [LightOccluder::shadow_tint]. |
shadow_opacity | f32 | As [LightOccluder::shadow_opacity]. |
self_shadow | bool | As [LightOccluder::self_shadow]. |
shadows_casters | bool | As [LightOccluder::shadows_casters]. |
shadow_length | f32 | As [LightOccluder::shadow_length]. |
cache | crate::tilemap::shapes::OutlineCache | The merged outlines, kept until the tiles or shapes change. Never saved. |
TilemapCollider
Section titled “TilemapCollider”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.
| Field | Type | Description |
|---|---|---|
solid | Vec<String> | Layers whose every tile is solid. |
one_way | Vec<String> | Layers whose tiles are one-way platforms (see Collider2D::one_way). |
Joint2D
Section titled “Joint2D”Joint component — connects this entity’s body to another entity’s body.
Scene JSON key: joint
| Field | Type | Description |
|---|---|---|
target_entity | String | Name of the target entity to connect to. |
joint_type | JointType | The joint type and its parameters. |
collide_connected | bool | Whether connected bodies can collide with each other (default: false). |
break_force | f32 | The 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_torque | f32 | The 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. |
AudioSource
Section titled “AudioSource”Audio source component — attaches a sound clip to an entity.
Scene JSON key: audio_source
| Field | Type | Description |
|---|---|---|
clip_id | String | Asset path for the audio clip (e.g. “sounds/jump.ogg”). |
volume | f32 | Volume (0.0 = silent, 1.0 = full). |
pitch | f32 | Pitch multiplier (1.0 = normal). |
looping | bool | Whether this clip loops. |
started | bool | Set once the source has been started, so autoplay fires exactly once. Runtime state; never serialized. |
auto_play | bool | If true, start playing automatically when the entity spawns. |
bus_id | Option<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). |
spatial | bool | Heard 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_distance | f32 | Full volume within this distance of the listener. |
max_distance | f32 | Silent from this distance. |
pan_width | f32 | A source this far to one side is panned all the way to it. |
occlusion | f32 | The 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. |
muffle | f32 | Its low-pass cutoff behind one wall, in hertz - each further wall halves it (0: no muffle). |
GuiComponent
Section titled “GuiComponent”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
| Field | Type | Description |
|---|---|---|
gui_id | String | Asset ID of the .gui document (e.g. “ui/hud.gui”). |
visible | bool | Whether this GUI is currently visible. |
variables | HashMap<String, serde_json::Value> | Dynamic variables that scripts can bind to GUI nodes (e.g. { "score": 42, "health": 0.75 }). |
focused_node_id | Option<String> | ID of the currently focused node (for keyboard navigation). None means no node has focus. |
nav_focus | bool | Runtime 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_id | Option<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_down | bool | Runtime state: the pointer is down over hovered_node_id - its pressed_color. |
hover_t | f32 | Runtime 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_ui | Option<(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_click | Option<(f32, bool)> | |
slider_drag | Option<(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. |
MaterialComponent
Section titled “MaterialComponent”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
| Field | Type | Description |
|---|---|---|
material_id | MaterialId | Name of the material in the MaterialLibrary (e.g. “default_sprite”, “additive”). |
blend_mode | Option<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. |
uniforms | HashMap<String, UniformValue> | Per-entity uniform overrides — merged on top of the material’s base uniforms. |
normal_map_id | Option<String> | Optional normal map texture for per-pixel lighting (unit 1). |
specular | f32 | How 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. |
shininess | f32 | How tight the highlight is: 1 a broad sheen, 128 a pin-point glint. |
PointLight2D
Section titled “PointLight2D”A point light that illuminates a radial area around the entity’s position.
Scene JSON key: point_light
| Field | Type | Description |
|---|---|---|
color | [f32; 3] | Light color (RGB). |
intensity | f32 | Light brightness multiplier. |
radius | f32 | Maximum reach in world units. |
falloff | f32 | Falloff exponent (1.0 = linear, 2.0 = quadratic). |
shadow_softness | f32 | Apparent 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. |
height | f32 | How 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_shadows | bool | Whether 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. |
cookie | String | A 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_scale | f32 | How 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). |
turn | f32 | The entity’s turn, which turns the cookie - filled from its world transform by gather_lights, never authored or saved. |
light_mask | u32 | The 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. |
subtract | bool | Takes 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. |
DirectionalLight2D
Section titled “DirectionalLight2D”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.
| Field | Type | Description |
|---|---|---|
color | [f32; 3] | Light color (RGB). |
intensity | f32 | Light brightness multiplier. |
direction | f32 | Which way the light travels, in radians (0 is towards +x, -PI/2 straight down the screen, the default). The entity’s rotation is added. |
height | f32 | How 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. |
softness | f32 | The sun’s apparent size, in radians: how soft the edges of its shadows are, widening with distance from the occluder. 0 is hard. |
casts_shadows | bool | Whether occluders shadow this light. |
light_mask | u32 | The 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. |
SpotLight2D
Section titled “SpotLight2D”A directional cone light attached to an entity.
Scene JSON key: spot_light
| Field | Type | Description |
|---|---|---|
color | [f32; 3] | Light color (RGB). |
intensity | f32 | Light brightness multiplier. |
radius | f32 | Maximum reach in world units. |
angle | f32 | Cone half-angle in radians. |
direction | f32 | Direction angle in radians (0 = right, π/2 = up). |
falloff | f32 | Falloff exponent. |
shadow_softness | f32 | Apparent emitter radius in world units used for soft shadow penumbra (see [PointLight2D::shadow_softness]). |
height | f32 | How high the light sits above the plane, for normal maps - see [PointLight2D::height]. |
casts_shadows | bool | Whether occluders shadow this light - see [PointLight2D::casts_shadows]. |
cookie | String | A 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_scale | f32 | How 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_mask | u32 | The 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. |
subtract | bool | Takes 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. |
AmbientLight
Section titled “AmbientLight”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
| Field | Type | Description |
|---|---|---|
color | [f32; 3] | Ambient color (RGB). |
intensity | f32 | Ambient intensity (0 = total darkness, 1 = fully lit). |
AmbientOcclusion
Section titled “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. 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.
| Field | Type | Description |
|---|---|---|
radius | f32 | How far from an outline the darkening reaches, in world units. |
strength | f32 | How dark it gets at the outline itself: 0 none, 1 black. |
GlobalIllumination
Section titled “GlobalIllumination”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.
| Field | Type | Description |
|---|---|---|
strength | f32 | How much of the bounce is added: 1 is what reaches each pixel. |
scale | u32 | Frame 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. |
cascades | u32 | How many cascades - each reaches four times as far as the last. 0, the default, takes as many as cross the frame. |
ToonLighting
Section titled “ToonLighting”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).
| Field | Type | Description |
|---|---|---|
bands | u32 | How many steps light takes between dark and full (at 1.0). Above 1.0 in an HDR scene the steps continue. |
softness | f32 | How soft the edge between two bands is, as a fraction of a band: 0 is a hard line, 1 is smooth again. |
dither | f32 | Dithered 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). |
LightmapComponent
Section titled “LightmapComponent”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
| Field | Type | Description |
|---|---|---|
texture_id | String | Asset 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]. |
intensity | f32 | Intensity multiplier applied to the texture’s colour. |
size | [f32; 2] | World size of the quad, before the transform’s scale. |
LightOccluder
Section titled “LightOccluder”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
| Field | Type | Description |
|---|---|---|
enabled | bool | When 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_sprite | bool | Derive the blocking box from the entity’s SpriteComponent bounds instead of size. |
polygon | Vec<[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_opacity | f32 | How 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_shadow | bool | Whether 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_casters | bool | Whether 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_length | f32 | How 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_alpha | bool | Cast 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_cutoff | f32 | The alpha above which a pixel is part of the shape (0..1). |
alpha_tolerance | f32 | How 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. |
traced | Option<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. |
Conveyor
Section titled “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. 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.
| Field | Type | Description |
|---|---|---|
speed | f32 |
SpriteCollider
Section titled “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.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.
| Field | Type | Description |
|---|---|---|
alpha_cutoff | f32 | The alpha above which a pixel is solid (0..1). |
alpha_tolerance | f32 | How far, in texels, the outline may stray from the pixels’ edge - larger is fewer, larger convex pieces. |
key | u64 | What the written shape was traced from (texture, frame, cutoff, tolerance, scale); 0 until it is. Never saved. |
Velocity2D
Section titled “Velocity2D”2D velocity for simple kinematic motion (no physics engine needed).
Scene JSON key: velocity
| Field | Type | Description |
|---|---|---|
x | f32 | Horizontal velocity in pixels/sec. |
y | f32 | Vertical velocity in pixels/sec. |
gravity | f32 | Gravity acceleration in pixels/sec² (subtracted from y each frame). |
BatchScriptComponent
Section titled “BatchScriptComponent”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
| Field | Type | Description |
|---|---|---|
path | String | Path to the script module declaring an update(ctx, q) function. Relative to the project’s assets directory, same as [ScriptComponent::source_path]. |
lang | ScriptLang | Scripting language. Defaults to Js for back-compat with prefabs authored before Slice 4B.1. |
BounceArea
Section titled “BounceArea”Axis-aligned bounding box that constrains entity position and bounces velocity.
Scene JSON key: bounce_area
| Field | Type | Description |
|---|---|---|
min_x | f32 | |
min_y | f32 | |
max_x | f32 | |
max_y | f32 | |
damping | f32 | Energy preservation on bounce (0.0–1.0). 1.0 = perfectly elastic. |
Gravity2D
Section titled “Gravity2D”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
| Field | Type | Description |
|---|---|---|
x | f32 | |
y | f32 |
DespawnOffscreen
Section titled “DespawnOffscreen”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
| Field | Type | Description |
|---|---|---|
margin | f32 | How 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_only | bool | Only 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). |
seen | bool | Whether 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.
| Field | Type | Description |
|---|---|---|
lifetime | f32 | How long a point lives, in seconds - the length of the tail in time. |
width | f32 | Width at the head, in world units. |
width_end | f32 | Width 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_distance | f32 | How far the entity moves before a new point is recorded. Smaller is smoother and costs more points (at most [Trail::MAX_POINTS]). |
texture_id | String | Texture stretched along the ribbon; empty is plain colour. |
z_order | i32 | |
sort_layer | Option<String> | |
emitting | bool | Off: no new points are recorded and the ones there age out. |
blend_mode | crate::renderer::material::BlendMode | |
points | Vec<TrailPoint> | The recorded points, oldest first - kept by tick_trails, never authored. |
TrailPoint
Section titled “TrailPoint”One recorded point of a [Trail].
| Field | Type | Description |
|---|---|---|
position | Vec2 | |
age | f32 | Seconds since it was recorded. |
TouchControls
Section titled “TouchControls”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
| Field | Type | Description |
|---|---|---|
show | TouchControlsShow | When 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. |
opacity | f32 | Multiplies every control’s opacity, 0..1. |
controls | Vec<TouchControl> |
TouchStick
Section titled “TouchStick”A thumbstick pressing up to two of four directions (a diagonal is two).
| Field | Type | Description |
|---|---|---|
anchor | ScreenAnchor | |
offset | [f32; 2] | From the anchored edge inward, in UI pixels (see input::touch_controls::place). |
radius | f32 | |
dead_zone | f32 | How far from the centre, as a fraction of radius, before a direction presses. |
up | String | |
down | String | |
left | String | |
right | String |
TouchButton
Section titled “TouchButton”A round button holding one key down while pressed.
| Field | Type | Description |
|---|---|---|
anchor | ScreenAnchor | |
offset | [f32; 2] | |
radius | f32 | |
press | String | A key name or an input action. |
label | String | Drawn on the button; empty for none. |
Lifetime
Section titled “Lifetime”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
| Field | Type | Description |
|---|---|---|
remaining | f32 | Remaining time in seconds. When this reaches zero, on_expire fires. |
on_expire | ExpireAction |
Acceleration2D
Section titled “Acceleration2D”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
| Field | Type | Description |
|---|---|---|
x | f32 | |
y | f32 |
Drag2D
Section titled “Drag2D”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
| Field | Type | Description |
|---|---|---|
x | f32 | |
y | f32 |
Force2D
Section titled “Force2D”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
| Field | Type | Description |
|---|---|---|
x | f32 | |
y | f32 |
Wind2D
Section titled “Wind2D”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
| Field | Type | Description |
|---|---|---|
x | f32 | |
y | f32 |
Impulse2D
Section titled “Impulse2D”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
| Field | Type | Description |
|---|---|---|
x | f32 | |
y | f32 |
Torque2D
Section titled “Torque2D”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
| Field | Type | Description |
|---|---|---|
x | f32 | |
y | f32 |
MaxSpeed
Section titled “MaxSpeed”Clamps the magnitude of Velocity2D to at most value (pixels/sec). Run after gravity/acceleration integration, before bounce.
Scene JSON key: max_speed
| Field | Type | Description |
|---|---|---|
value | f32 |
Oscillator
Section titled “Oscillator”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
| Field | Type | Description |
|---|---|---|
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. |
elapsed | f32 | Accumulated time in seconds. |
center | Option<[f32; 2]> | Captured oscillation center. None means “capture from current Transform.position on first tick”. |
FollowTarget
Section titled “FollowTarget”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
| Field | Type | Description |
|---|---|---|
target | EntityRef | |
offset | [f32; 2] | Constant offset added to the target’s position. |
max_speed | f32 | |
stop_distance | f32 | Don’t move when within this many world units of the target+offset. |
SeekTarget
Section titled “SeekTarget”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
| Field | Type | Description |
|---|---|---|
target | EntityRef | |
accel | f32 | |
max_speed | f32 | |
slowing_radius | f32 | Step 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
| Field | Type | Description |
|---|---|---|
target | TweenTarget | |
from | f32 | |
to | f32 | |
duration | f32 | |
elapsed | f32 | |
ease | Ease | |
on_complete | TweenComplete |
PatrolPathRef
Section titled “PatrolPathRef”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
| Field | Type | Description |
|---|---|---|
path_id | String | Asset ID into the Path2DLibrary. |
speed | f32 | |
index | usize | Index of the waypoint we are currently moving toward. |
mode | PatrolMode | |
forward | bool | true = walking 0 → N, false = walking N → 0 (only used by PingPong). |
ShadowCaster
Section titled “ShadowCaster”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
| Field | Type | Description |
|---|---|---|
enabled | bool | Whether 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). |
height | f32 | Fake elevation — multiplies offset to push shadow further. |
scale | [f32; 2] | Shadow scale relative to sprite size. |
skew | f32 | Horizontal shear for perspective-style shadows. |
VectorPath
Section titled “VectorPath”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
| Field | Type | Description |
|---|---|---|
commands | Vec<PathCommand> | Ordered list of path drawing commands. |
fill_color | Option<[f32; 4]> | Fill color (RGBA). If None, the path is not filled. |
stroke_color | Option<[f32; 4]> | Stroke/outline color (RGBA). If None, the path is not stroked. |
stroke_width | f32 | Stroke width in world units. |
z_order | i32 | Draw order — higher z_order draws on top. |
closed | bool | Whether the path is closed (auto-close last sub-path). |
sort_layer | Option<String> | Optional sort-layer name (see SortLayers). |
InputAxis2D
Section titled “InputAxis2D”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
| Field | Type | Description |
|---|---|---|
x_neg | String | Action name driving negative X (e.g. "move_left"). |
x_pos | String | Action name driving positive X (e.g. "move_right"). |
y_neg | String | Action name driving negative Y (e.g. "move_down"). |
y_pos | String | Action name driving positive Y (e.g. "move_up"). |
scale | f32 | Scale 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. |
normalize | bool | When true and both axes are non-zero, normalize the (pre-scale) axis vector to unit length so diagonal movement isn’t √2 faster. |
current | Vec2 | Computed each frame by [update_input_actions]. Not serialized. |
drive | AxisDrive | What 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. |
InputActionImpulse
Section titled “InputActionImpulse”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
| Field | Type | Description |
|---|---|---|
action | String | Action name to listen for (e.g. "jump"). |
impulse_x | Option<f32> | Replacement X velocity on trigger. |
impulse_y | Option<f32> | Replacement Y velocity on trigger. |
BehaviorGraphComponent
Section titled “BehaviorGraphComponent”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
| Field | Type | Description |
|---|---|---|
graph_path | String | Path to a .behavior asset, resolved against the project’s assets directory (same convention as [ScriptComponent::source_path]). |
overrides | std::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. |
TriggerArea2D
Section titled “TriggerArea2D”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
| Field | Type | Description |
|---|---|---|
shape | TriggerShape | |
match_tags | Vec<String> | Only entities carrying at least one of these tags are considered for overlap. Empty list = match every entity with a Transform. |
TriggerOverlaps
Section titled “TriggerOverlaps”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.
| Field | Type | Description |
|---|---|---|
entered | Vec<u64> | |
staying | Vec<u64> | |
exited | Vec<u64> |
Health
Section titled “Health”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
| Field | Type | Description |
|---|---|---|
current | f32 | |
max | f32 | |
invuln_time | f32 | Seconds of invulnerability granted after a hit (i-frames). 0.0 = hittable every frame a hitbox overlaps. |
invuln_timer | f32 | Runtime i-frame countdown; not serialized. |
Hitbox
Section titled “Hitbox”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
| Field | Type | Description |
|---|---|---|
damage | f32 | |
knockback | f32 | Positional knockback distance applied to the victim, directed away from this hitbox’s entity. 0.0 = none. |
active | bool | When false the hitbox deals no damage (toggle for timed swings). |
retrigger | f32 | Seconds 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_hit | bool | Despawn 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. |
cooldowns | HashMap<u64, f32> | Runtime per-victim cooldown timers (victim to_bits -> seconds left). Not serialized. |
DespawnOnDeath
Section titled “DespawnOnDeath”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
Obstacle
Section titled “Obstacle”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
| Field | Type | Description |
|---|---|---|
radius | f32 | Keep-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. |
BulkEntity
Section titled “BulkEntity”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.)
| Field | Type | Description |
|---|---|---|
radius | f32 | Soft 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. |
HealthBar
Section titled “HealthBar”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
| Field | Type | Description |
|---|---|---|
width | f32 | Full-width of the bar in world units. |
height | f32 | Bar height in world units. |
y_offset | f32 | Vertical offset above the owner’s origin. |
hide_when_full | bool | Hide the bar while HP is full (typical for enemies). |
HealthBarView
Section titled “HealthBarView”Runtime-only back-link from a health-bar sprite to its owner. Managed entirely by sync_health_bars; not serialized and not authorable.
| Field | Type | Description |
|---|---|---|
owner | hecs::Entity | |
is_fill | bool | true for the colored fill quad, false for the dark backing. |
NameTagView
Section titled “NameTagView”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).
| Field | Type | Description |
|---|---|---|
owner | hecs::Entity |
WrapBounds
Section titled “WrapBounds”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
| Field | Type | Description |
|---|---|---|
min | [f32; 2] | |
max | [f32; 2] |
ClampBounds
Section titled “ClampBounds”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
| Field | Type | Description |
|---|---|---|
min | [f32; 2] | |
max | [f32; 2] | |
zero_velocity_on_hit | bool | Zero 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. |
DespawnOutOfBounds
Section titled “DespawnOutOfBounds”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
| Field | Type | Description |
|---|---|---|
min | [f32; 2] | |
max | [f32; 2] | |
margin | f32 | Extra 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). |
FaceVelocity
Section titled “FaceVelocity”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
| Field | Type | Description |
|---|---|---|
offset_radians | f32 | Radians added to the computed atan2(vy, vx) heading. Default 0.0 assumes sprite art faces +X. |
min_speed | f32 | Skip rotation when ` |
turn_rate | f32 | How 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. |
AngularVelocity
Section titled “AngularVelocity”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
| Field | Type | Description |
|---|---|---|
omega | f32 | Spin rate in radians per second. Positive = counter-clockwise in the engine’s Y-up convention. |
Flicker
Section titled “Flicker”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
| Field | Type | Description |
|---|---|---|
frequency | f32 | Toggles per second (e.g. 10.0 = 5 on, 5 off per second). |
duration | f32 | Total flicker time in seconds. Component is removed after this. |
elapsed | f32 | Time elapsed inside this flicker. Initialise to 0.0. |
original_alpha | Option<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
| Field | Type | Description |
|---|---|---|
intensity | f32 | Peak displacement magnitude in world units at start. |
frequency | f32 | New random offset samples per second. |
duration | f32 | Total shake duration in seconds. |
elapsed | f32 | Time elapsed inside this shake. Initialise to 0.0. |
offset | Option<[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. |
seed | Option<u32> | Internal PRNG state seeded on first tick — kept on the component so each entity has its own deterministic stream. |
SpriteFill
Section titled “SpriteFill”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).
| Variant | Fields |
|---|---|
Stretch | — |
NineSlice | left: f32, right: f32, top: f32, bottom: f32, source_px: Option<[f32; 2]> |
Tile | amount: f32, from: FillFrom |
Radial | amount: f32, start: f32, clockwise: bool |
FillFrom
Section titled “FillFrom”Which side a [SpriteFill::Bar] fills from.
| Variant | Fields |
|---|---|
Left | — |
Right | — |
Bottom | — |
Top | — |
ScriptLanguage
Section titled “ScriptLanguage”Which scripting language a script uses.
| Variant | Fields |
|---|---|
Lua | — |
JavaScript | — |
BodyType
Section titled “BodyType”Rigid body type for physics simulation.
| Variant | Fields |
|---|---|
Dynamic | — |
Static | — |
Kinematic | — |
ColliderShape
Section titled “ColliderShape”Shape type for a 2D collider.
| Variant | Fields |
|---|---|
Box | vertices: Vec<[f32; 2]>, loops: Vec<u32> |
JointType
Section titled “JointType”Joint type for connecting two physics bodies.
| Variant | Fields |
|---|---|
Revolute | anchor: [f32; 2], anchor_b: [f32; 2], limits: Option<[f32; 2]>, motor_speed: f32, motor_max_torque: f32 |
Fixed | anchor_a: [f32; 2], anchor_b: [f32; 2] |
Prismatic | anchor: [f32; 2], anchor_b: [f32; 2], axis: [f32; 2], limits: Option<[f32; 2]>, motor_speed: f32, motor_max_force: f32 |
Rope | anchor_a: [f32; 2], anchor_b: [f32; 2], max_distance: f32 |
Spring | anchor_a: [f32; 2], anchor_b: [f32; 2], rest_length: f32, stiffness: f32, damping: f32 |
Wheel | anchor: [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 |
Motor | linear_offset: [f32; 2], angular_offset: f32, max_force: f32, max_torque: f32, frequency: f32, damping_ratio: f32 |
Friction | anchor_a: [f32; 2], anchor_b: [f32; 2], max_force: f32, max_torque: f32 |
Target | anchor: [f32; 2], target: [f32; 2], frequency: f32, damping_ratio: f32, max_force: f32 |
ScriptLang
Section titled “ScriptLang”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.
| Variant | Fields |
|---|---|
Js | — |
Lua | — |
ExpireAction
Section titled “ExpireAction”What to do when a Lifetime’s remaining reaches zero.
| Variant | Fields |
|---|---|
Despawn | — |
TouchControlsShow
Section titled “TouchControlsShow”When [TouchControls] show.
| Variant | Fields |
|---|---|
Touch | — |
Always | — |
Never | — |
TouchControl
Section titled “TouchControl”One on-screen control.
| Variant | Fields |
|---|
ScreenAnchor
Section titled “ScreenAnchor”The screen edge or corner a touch control is placed from.
| Variant | Fields |
|---|---|
TopLeft | — |
Top | — |
TopRight | — |
Left | — |
Center | — |
Right | — |
BottomLeft | — |
Bottom | — |
BottomRight | — |
EntityRef
Section titled “EntityRef”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.
| Variant | Fields |
|---|---|
Player | — |
SelfEntity | — |
TweenTarget
Section titled “TweenTarget”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.
| Variant | Fields |
|---|---|
PositionX | — |
PositionY | — |
Rotation | — |
ScaleX | — |
ScaleY | — |
Opacity | — |
Easing curve. apply(t) maps t ∈ [0, 1] → eased [0, 1].
| Variant | Fields |
|---|---|
Linear | — |
EaseInQuad | — |
EaseOutQuad | — |
EaseInOutQuad | — |
TweenComplete
Section titled “TweenComplete”What to do once a Tween reaches its end.
| Variant | Fields |
|---|---|
Stop | — |
Loop | — |
PingPong | — |
PatrolMode
Section titled “PatrolMode”How a PatrolPathRef traverses its waypoints once it hits the end.
| Variant | Fields |
|---|---|
Loop | — |
PingPong | — |
Once | — |
PathCommand
Section titled “PathCommand”A single path command — moveto, lineto, bezier curves, or close.
| Variant | Fields |
|---|---|
MoveTo | c1x: f32, c1y: f32, c2x: f32, c2y: f32, x: f32, y: f32 |
Close | — |
AxisDrive
Section titled “AxisDrive”What an [InputAxis2D] drives on its own entity.
| Variant | Fields |
|---|---|
None | — |
Velocity | — |
Acceleration | — |
TriggerShape
Section titled “TriggerShape”Shape of a [TriggerArea2D].
| Variant | Fields |
|---|---|
Aabb | — |