Output Schema
Open the editor to try output types live.
Return a FrameResult object from your entry file. Every property is optional — return only what you need.
return {
sprites: [...],
particles: [...],
lines: [...],
vectors: [...],
circles: [...],
arcs: [...],
bars: [...],
text: { ... },
}Rendering Order
Drawn back to front:
spriteslinescirclesarcsbarsvectorsparticlestext(HUD overlay)
Later layers sit on top of earlier ones.
Sprites
Images from the built-in asset library. x / y are the center of the image. Draw a background image first, then sprites, so it sits behind everything.
return {
sprites: [
{ asset: 'ship-0', x: w / 2, y: h / 2, w: 64, h: 64 },
{ asset: 'coin', x: 120, y: 200 },
],
}| Property | Type | Required | Default | Description |
|---|---|---|---|---|
asset | string | yes | — | Asset id, e.g. 'ship-0', 'coin' |
x | number | yes | — | Center X |
y | number | yes | — | Center Y |
w | number | no | natural width | Display width |
h | number | no | natural height | Display height |
rotation | number | no | 0 | Rotation in radians |
flipX | boolean | no | false | Mirror horizontally |
flipY | boolean | no | false | Mirror vertically |
opacity | number | no | 1 | 0–1 transparency |
src | object | no | whole image | Source crop { sx, sy, sw, sh } for sprite sheets |
Sprites whose image is still loading are skipped until ready, so there is no error frame.
GAME BUILDING
src crops let you pull one frame out of a sprite sheet. Use assets.frame(id, index, cols) to compute the crop, or assets.tiles(...) to lay out a whole grid. See Assets.
Particles
Filled circles with optional labels.
return {
particles: [
{ x: 100, y: 200, r: 8, color: '#8B5CF6', label: 'ball' },
],
}| Property | Type | Required | Default | Description |
|---|---|---|---|---|
x | number | yes | — | Center X |
y | number | yes | — | Center Y |
r | number | no | 6 | Radius in pixels |
color | string | no | #8B5CF6 | Fill color (CSS) |
label | string | no | — | Label to the right of the circle |
TIP
Use particles for moving bodies — balls, planets, boids. Default purple reads well on the dark canvas.
Lines
Solid or dashed segments.
return {
lines: [
{ x1: 0, y1: 0, x2: 200, y2: 150, color: '#F59E0B', width: 2, dashed: true },
],
}| Property | Type | Required | Default | Description |
|---|---|---|---|---|
x1 | number | yes | — | Start X |
y1 | number | yes | — | Start Y |
x2 | number | yes | — | End X |
y2 | number | yes | — | End Y |
color | string | no | #ffffff | Stroke color |
width | number | no | 2 | Line width in pixels |
dashed | boolean | no | false | Dash pattern 6 / gap 4 |
TIP
Store a trail in state.trail and map it to lines each frame. Cap length for performance.
Vectors
Arrows from an origin, with arrowheads.
return {
vectors: [
{ x: 100, y: -50, ox: 200, oy: 200, color: '#22C55E', label: 'v' },
],
}| Property | Type | Required | Default | Description |
|---|---|---|---|---|
x | number | yes | — | Horizontal component from origin |
y | number | yes | — | Vertical component from origin |
ox | number | no | 0 | Origin X |
oy | number | no | 0 | Origin Y |
color | string | no | #22C55E | Arrow color |
label | string | no | — | Label near the arrowhead |
Arrowhead size: min(12, length * 0.3) pixels.
MATH
x / y are components, not endpoints. Velocity (vx, vy) at (px, py) → { x: vx, y: vy, ox: px, oy: py }. Scale (e.g. vx * 0.3) so arrows stay readable.
Circles
Outlines, optionally filled.
return {
circles: [
{ cx: 200, cy: 200, r: 60, color: 'rgba(255,255,255,0.2)', fill: 'rgba(139,92,246,0.1)' },
],
}| Property | Type | Required | Default | Description |
|---|---|---|---|---|
cx | number | yes | — | Center X |
cy | number | yes | — | Center Y |
r | number | yes | — | Radius |
color | string | no | rgba(255,255,255,0.2) | Stroke |
fill | string | no | — | Fill (omit for hollow) |
TIP
Good for orbits, influence radii, and collision bounds. Semi-transparent fills keep particles visible underneath.
Arcs
Partial circles for angles.
return {
arcs: [
{ cx: 200, cy: 200, r: 30, startAngle: 0, endAngle: 1.57, color: '#F59E0B', width: 2 },
],
}| Property | Type | Required | Default | Description |
|---|---|---|---|---|
cx | number | yes | — | Center X |
cy | number | yes | — | Center Y |
r | number | yes | — | Radius |
startAngle | number | yes | — | Start radians (0 = right) |
endAngle | number | yes | — | End radians |
color | string | no | #F59E0B | Stroke |
width | number | no | 2 | Line width |
MATH
Canvas angles: 0 right, π/2 down (Y increases downward), π left, 3π/2 up. Math textbooks often use Y-up — flip with -Math.sin when matching textbook diagrams.
Bars
Filled rectangles for energy diagrams, histograms, or gauges.
const KE = 0.5 * mass * state.v * state.v;
const PE = 0.5 * k * state.x * state.x;
return {
bars: [
{ x: 30, y: 300 - KE * 0.01, w: 40, h: KE * 0.01, color: '#EF4444', label: 'KE' },
{ x: 90, y: 300 - PE * 0.01, w: 40, h: PE * 0.01, color: '#3B82F6', label: 'PE' },
],
}| Property | Type | Required | Default | Description |
|---|---|---|---|---|
x | number | yes | — | Top-left X |
y | number | yes | — | Top-left Y |
w | number | yes | — | Width |
h | number | yes | — | Height (grows downward) |
color | string | no | #8B5CF6 | Fill |
label | string | no | — | Text above the bar |
TIP
Normalize heights: h: (energy / maxEnergy) * 150 so bars stay on-screen.
Text
HUD key-value panel in the top-right.
return {
text: {
time: time.toFixed(1) + 's',
velocity: Math.sqrt(state.vx ** 2 + state.vy ** 2).toFixed(1),
particles: state.particles.length,
},
}| Keys / values | Type | Description |
|---|---|---|
| keys | string | Labels (monospace) |
| values | string | number | Shown after each label |
Panel at (w - 210, 5), width 205px, ~18px per row. Entries appear in object insertion order (not sorted).
WARNING
The HUD covers the top-right ~210px. Keep important geometry out of that corner.
Returning Nothing
null, undefined, or no return clears the frame to grid + axes only:
if (!state.ready) return null;Useful for conditional rendering or waiting on init.