ADR-020: A pack frame is placed at a whole multiple and drawn over the procedural surround
Date: 2026-08-18
Status: Accepted
Context
PBI-122 is the first place a pack frame reaches the panel, and it has to answer two questions the
earlier presentation work could leave open. A 64 by 64 canonical sprite has to land somewhere on a
240 by 135 surface, and the pixels the one-bit mask leaves transparent have to show something.
Section 8.5 of the architecture states
both that scaling is integer and that the sprites reach the panel through the same fit rule the
desktop uses. Those two are not the same rule here. pet_layout_fit preserves the aspect ratio and
fills the output, which places a 64 by 64 sprite at 135 by 135 and a scale of 2.109. The desktop
hands that rectangle to a GPU that samples it. A software composer on this panel would have to widen
some source pixels to two panel pixels and others to three, which is visible on antialiased pixel
art at this size.
The transparency question is narrower. The mask marks a pixel as opaque or not, and something has to be under the sprite before it is drawn.
Decision
A pack frame is placed by pet_layout_fit_whole, which is a new operation of components/layout. It
takes the largest whole multiple both extents allow, centres the result, and refuses a canvas the
output cannot take even once. The 64 by 64 sprites therefore land at 128 by 128 in the middle of the
panel, and every sprite pixel covers exactly the same 2 by 2 square.
The refusal is the geometry arm of the resolution chain in section 6.4. A frame the surface cannot take whole reaches the procedural fallback rather than a shrunk or partial one, so no arm of the chain produces a frame the composition had to compromise.
The surround under a pack frame is the procedural appearance of the same role. The frontend clears the surface with that appearance’s letterbox colour and then draws the sprite, so a transparent mask bit shows the calm surround rather than whatever the buffer held. The procedural body is not drawn under a pack frame, because the sprite is what carries the role once one exists.
The procedural fallback keeps pet_layout_fit. It has no pixel grid to keep aligned, so filling the
surface is right for it, and the appearance PBI-116 observed on the board is unchanged.
Consequences
The panel shows the canonical pet at a clean doubling with a letterbox band of a few pixels at each edge, which is the cost of the alignment. A larger panel, or a sprite set drawn for one, changes the multiple and nothing else.
components/layout gains one operation and one status value, and both frontends can reach the rule.
The SDL frontend is untouched, because it composes through a renderer that samples for it.
The pack path and the fallback path share the surround, so a role reads the same whether or not its frame resolved. That is what makes a pack refusal cost the canonical appearance and nothing else.
A frontend surface smaller than one sprite presents the procedural appearance forever rather than a downscaled pet. No supported board is in that position, and the release records the rule rather than discovering it on a future panel.
Alternatives considered
Sample the sprite into the rectangle pet_layout_fit produces. Rejected because a 2.109 scale
widens neighbouring source pixels unevenly, which the canonical antialiased sprites show as a ragged
edge. It is the cheaper rule to state and the worse one to look at.
Downscale a sprite larger than the surface. Rejected because averaging or dropping pixels is a second scaling mechanism for a case no supported board has, and the fallback already covers it.
Pre-compose the sprites onto a background in the converter and drop the mask. Rejected by ADR-019 when the format was decided, and this decision keeps that boundary. The pack carries what the pet looks like and the frontend decides what is behind it.
Draw the procedural body under the sprite as well. Rejected because two role appearances would be visible at once wherever the mask is clear, and the fallback body exists to carry a role the pack cannot.
References
- ADR-018, the buffer this composes into
- ADR-019, the mask and pixel encoding this reads
- The pack contract, sections 7.1 and 7.2
- Architecture for v0.5.0, sections 6.4 and 8.5
- Epics,
E35 - PBIs,
PBI-122