§55's frame?: Rectangle2 — the sub-rectangle of the texture a sprite
samples, in texels (R-29, 2026-08-08).
// a 16 × 16 cell of a 128 × 128 sheet, third column, second row from the bottom sprite.setFrame(32, 16, 16, 16);
Why texels, and where the origin is
§55 names the type Rectangle2 and gives no units; the two candidates were
texels and normalized uv, and this is texels because:
it is the unit every atlas packer, sprite sheet, and glyph atlas already
emits, so the common case involves no conversion and no rounding;
it makes §85's containment check mean something — a frame is validated
against the texture it indexes, which in normalized units degenerates to
"is it inside 0…1" and never consults the texture at all;
it keeps the field readable: (32, 16, 16, 16) names a cell, (0.25, 0.125, 0.125, 0.125) names arithmetic.
The price is a coupling — a frame is only meaningful against a texture of a
particular size — and it is paid where Sprite.frame documents it.
The origin is the bottom-left texel and +Y is up, which is not a choice:
MaterialTexture.data documents row 0 as v = 0, the sprite shader
documents v = 0 as the quad's bottom edge, and §7a puts +Y up in 2D exactly
as in 3D. A producer whose rectangles are top-left-origin (a decoded PNG
sheet, most packer sidecars) flips with y = textureHeight - top - height
where it adapts them, which is the same place it already flips the texels.
A producer that has normalized uv instead — @fourjs/text's TextQuad
carries u0/v0/u1/v1 — multiplies: x = u0 · texture.width,
width = (u1 - u0) · texture.width, and the same in Y.
Every field is readonly: a frame is read by the render list and the backend
and written only through Sprite.setFrame, which validates.
§55's
frame?: Rectangle2— the sub-rectangle of the texture a sprite samples, in texels (R-29, 2026-08-08).Why texels, and where the origin is
§55 names the type
Rectangle2and gives no units; the two candidates were texels and normalized uv, and this is texels because:0…1" and never consults the texture at all;(32, 16, 16, 16)names a cell,(0.25, 0.125, 0.125, 0.125)names arithmetic.The price is a coupling — a frame is only meaningful against a texture of a particular size — and it is paid where Sprite.frame documents it.
The origin is the bottom-left texel and +Y is up, which is not a choice:
MaterialTexture.datadocuments row 0 asv = 0, the sprite shader documentsv = 0as the quad's bottom edge, and §7a puts +Y up in 2D exactly as in 3D. A producer whose rectangles are top-left-origin (a decoded PNG sheet, most packer sidecars) flips withy = textureHeight - top - heightwhere it adapts them, which is the same place it already flips the texels.A producer that has normalized uv instead —
@fourjs/text'sTextQuadcarriesu0/v0/u1/v1— multiplies:x = u0 · texture.width,width = (u1 - u0) · texture.width, and the same in Y.Every field is
readonly: a frame is read by the render list and the backend and written only through Sprite.setFrame, which validates.