Skip to content

Spritesheet rendering leaks adjacent frame pixels due to inconsistent rounding between container size and background offset #5

Description

@wind4gis

Problem

When importing pets from external sources (e.g. Petdex), the bottom of the pet sprite sometimes shows a thin strip of the next row/frame from the spritesheet. It looks like the GIF is "leaking" into the next emotion frame.

Root Cause

The spritesheet rendering in PetSprite.tsx uses background-position to select frames, but the rounding strategy is inconsistent between:

  1. Container size — getPetSpriteSize() uses Math.ceil:

    width: Math.ceil(PET_ATLAS.cellWidth * renderScale),
    height: Math.ceil(PET_ATLAS.cellHeight * renderScale),
  2. Background offset — getPetFrameOffset() uses raw floats without rounding:

    x: -safeFrame * PET_ATLAS.cellWidth * renderScale,
    y: -animation.row * PET_ATLAS.cellHeight * renderScale,
  3. Background size — PetSprite.tsx also uses raw floats:

    backgroundSize: `${PET_ATLAS.width * renderScale}px ${PET_ATLAS.height * renderScale}px`,

When renderScale produces non-integer pixel values (e.g. cellHeight * renderScale = 155.7), the container is rounded up by Math.ceil to 156 px, but the background offset stays at the fractional position. Browser sub-pixel rendering then causes the bottom/right edge to reveal 1 px (or a fraction) of the adjacent frame in the spritesheet atlas.

Additional Concern: Hard-coded Atlas Dimensions

PET_ATLAS is hard-coded globally:

export const PET_ATLAS = {
  width: 1536,
  height: 1872,
  columns: 8,
  rows: 9,
  cellWidth: 192,
  cellHeight: 208,
} as const;

This means imported pets (e.g. from Petdex or Codex Pets) are forced into the same grid even if their actual spritesheets use different cell dimensions. If a Petdex pack has a cell height smaller than 208, the offset math will cross row boundaries and make the "leak" much worse.

Suggested Fixes

Short-term

Align all spritesheet math to the same rounding strategy, e.g. Math.round:

  • In getPetFrameOffset():

    x: Math.round(-safeFrame * PET_ATLAS.cellWidth * renderScale),
    y: Math.round(-animation.row * PET_ATLAS.cellHeight * renderScale),
  • In PetSprite.tsx backgroundSize:

    backgroundSize: `${Math.round(PET_ATLAS.width * renderScale)}px ${Math.round(PET_ATLAS.height * renderScale)}px`,

Long-term

Allow pet.json to declare atlas metadata (cellWidth, cellHeight, columns, rows) so that imported pets from different sources can be rendered with their own correct grid dimensions instead of being forced into the hard-coded 192×208 atlas.

Environment

  • OpenPet version: latest master (as of 2026-08-14)
  • Platform: observed on imported Petdex packs

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions