Skip to main content
Glama

cast_shadow

Cast a pixel-art ground shadow away from a light source, built from palette ramp steps so colors stay conformant in indexed exports and tilesets.

Instructions

Lay a subject's shadow on the ground, away from the light and made of ramp steps.

`add_drop_shadow` offsets a copy of the artwork and tints it, which is a sticker of
the subject floating beside it. A cast shadow is a different thing: it falls on a
*surface*, away from the light, and flattens as it goes, so on the ground it is a
foreshortened ellipse under the subject rather than a second copy of its silhouette.

Built entirely of ramp steps, which is the point. A shadow made by multiplying alpha
lands every pixel of it between palette entries, and then `palette_conformance` drops
and nothing downstream holds together: indexed export, tileset reuse, a consistent
look between two sprites. The core is `ramp[0]` and each pixel of `softness` around it
is one step lighter, so the whole effect is ramp entries arranged in space.

Where it falls: the direction is away from `light_angle`, and the length comes from
`light_height` as the actual cotangent of the light's elevation. An overhead light
casts an ellipse straight underneath; a low light throws it far to one side.

Onto what: `ground_layer`. Name the layer holding the floor and the shadow is clipped
to it, so it cannot run off the edge of a platform and hang in the air, and if that
layer has nothing where the shadow would land the call is refused rather than drawing
a shadow onto nothing.

Args:
    layer: The layer casting the shadow. Its cel is never modified. A background
        layer is refused: it is opaque and covers the canvas, so it has no silhouette
        to cast. `convert_background_to_layer` first if the subject is one.
    ramp: Colours darkest first, normally the *ground's* ramp rather than the
        subject's, since the shadow is a darkening of the surface it lies on.
        Required, and deliberately so: a shadow built out of alpha instead is
        `add_drop_shadow`, which already exists.
    light_angle: Degrees. 0 is from the right, 90 from above, 135 from the upper left.
        The shadow falls the opposite way.
    light_height: The light's elevation, above 0 and up to 1. 1.0 is directly
        overhead and casts no length at all; small values are a low sun and throw a
        long shadow. This is the control that changes the shadow's length.
    ground_y: The row the shadow lies on. Defaults to the subject's own contact row,
        the lowest row it has a pixel on, which is the same measurement
        `validate_loop` reports as `contact_rows`. Refused if it sits above that row,
        because a floor running through the subject is not a floor.
    ground_layer: The layer holding the surface. When given, the shadow is clipped to
        that layer's pixels and the call is refused if there is nothing there to
        catch it. Consulted for its pixels, not for whether the picture shows them: a
        hidden layer, or one at a low opacity, still holds the pixels the clip asks
        about, so the shadow is drawn and the result reports what the floor actually
        is (see below) rather than the call being refused over it.
    softness: Pixels of penumbra around the core, each one ramp step lighter. 0 is a
        hard-edged shadow, 1 or 2 is the usual soft contact.
    opacity: The shadow *layer's* opacity, 0 to 255. Left at 255 the shadow's pixels
        are exactly ramp entries, which is what keeps conformance at 1.0; lowering it
        blends them with whatever is underneath and takes the composite off the ramp,
        so prefer a lighter ramp step over a lower opacity.
    new_layer: Name for the shadow's own layer, created directly below `layer`, or
        reused when a previous call of this tool made it and `frame` has no cel on it
        yet. That is how one shadow layer carries a whole walk cycle. A second call for
        the same layer *and* frame is refused, because two effects composited into one
        cel are a picture neither call describes, and so is a layer of that name this
        tool did not create.
    frame: Frame to build the shadow for, 1-based.

Returns the ellipse it used as `shadow_ellipse` (`[cx, cy, rx, ry]`), the
`contact_row` it measured, and `shadow_pixels`. A shadow that landed entirely off the
canvas is refused rather than reported as a success that drew nothing.

With a `ground_layer` the result also says what that floor is: `ground_layer_visible`,
`ground_layer_opacity`, and `ground_layer_hidden` naming the layer and any group whose
flag is off, which is present only when something is. A floor the picture does not
show, or shows faintly, is reported in `warnings` as well, because a crisp shadow on
an invisible surface is usually a layer named by mistake rather than an intent.

`clipped_pixels` is routinely large next to `shadow_pixels` when `ground_layer` is a
thin floor, and that is arithmetic rather than a fault: the ellipse is centred on the
contact row and so half of it lies above the floor's top edge, where there is no
surface. Pass `ground_y` at the floor's own top row to push it down, or leave
`ground_layer` out and let the subject hide the upper half.

A shadow is refused when rasterising it would allocate more points than the limit,
which is reachable from legal arguments on a large canvas: the ellipse's radii grow
with the subject's size and with how low the light sits, and the point list is built
in full before any pixel is drawn, so that cost is memory rather than patience. The
message names both radii and the remedy. `ellipse_points` in the result says how close
an accepted shadow came.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
rampYes
frameNoDefault: 1.
layerYes
opacityNoDefault: 255.
filenameYes
ground_yNo
softnessNoDefault: 1.
new_layerNoDefault: "shadow".shadow
light_angleNoDefault: 135.0.
ground_layerNo
light_heightNoDefault: 0.6.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Addedv0.10.0

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only declare readOnlyHint=false and openWorldHint=false; the description carries far more: the source cel is never modified, the new layer is created directly below `layer` and reused across frames, a second call for the same layer+frame is refused, a foreign layer of the same name is refused, and opacity below 255 breaks palette conformance. It also discloses the point-allocation limit and that the point list is built in full before drawing.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose and sibling differentiation before the mechanism and per-argument detail. Given 11 parameters and several non-obvious refusal rules, the length is largely justified, though several passages are rhetorically padded ('which is the point', 'a picture neither call describes') and could be tightened without losing information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema exists, and the description compensates by documenting the return fields (shadow_ellipse, contact_row, shadow_pixels, clipped_pixels, ellipse_points) and the conditional ground_layer_visible/opacity/hidden warnings. Return semantics, refusal semantics, and argument interactions are all present, leaving little an agent needs that is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is only 55%, but the description compensates heavily: ramp ordering (darkest first, ground's ramp), light_angle's degree convention, light_height as cotangent of elevation, ground_y default and refusal rule, ground_layer's clip semantics (including that hidden/low-opacity layers still hold pixels), softness as ramp steps lighter, and opacity's conformance tradeoff. `filename` is left implicit, but the semantic gaps are otherwise well covered.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource (lay a subject's shadow on the ground) plus the mechanism (made of ramp steps), and explicitly contrasts itself with `add_drop_shadow`, naming that sibling and explaining how the two effects differ. An agent can distinguish this from every other shadow/palette tool without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit routing to `add_drop_shadow` for alpha-based shadows, plus named prerequisites (`convert_background_to_layer` for background subjects), refusal conditions (background layer, ground_y above contact row, empty ground_layer, duplicate call on same layer+frame), and the offered remedy for large clipped_pixels. When-to-use and when-refused are both spelled out.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Deploy Server

Other Tools