Pet asset manifest v1
| Document version | Date | Summary |
|---|---|---|
| v1 | 2026-07-21 | Define the common manifest grammar, the pet presentation schema v1, and the development runtime asset layout |
| v2 | 2026-07-21 | Record the role limit, the line terminator and byte rules, and the implemented parser component |
| v3 | 2026-07-21 | Record the reusable asset-set layer and reader boundary |
| v4 | 2026-07-21 | Record the implemented asset set, its runtime copy, the signature check, and the fallback outcome |
Status: Accepted contract for implementation
Product version: v0.2.0
Related documents: architecture for v0.2.0, ADR-007, product specification, epics, and PBIs.
1. Purpose
This document is the normative engineering contract for the visual asset manifest introduced in
v0.2.0. It defines a common line-oriented manifest grammar, the version 1 schema for pet
presentation assets, the identifier, path, and count limits, and the development runtime asset layout.
The document defines the contract rather than implementing it. The parser, the reusable asset-set loading layer, the canonical images, and the runtime copy exist. The fallback rendering and the SDL image loading remain with the PBIs listed in section 13.
The asset source tree also carries its own shorter user-facing documentation. A person browsing
assets/ reads assets/README.md, which is git-tracked as the asset tree is.
This file holds the full detail for developers working with the whole repository.
2. Scope
The manifest maps canonical pet presentation images into animations and then into presentation roles that a frontend selects from Core-derived presentation state. It is a project-owned contract for this release rather than a user modding interface.
The manifest describes pet presentation assets only. Menu graphics, button states, icons, panels,
cursor details, fonts, and other application chrome are UI assets. Their state comes from application
navigation, focus, pointer hover, selection, text, localisation, and layout rather than from a
PetSnapshot. They must not be placed in the pet presentation manifest. A future UI asset layer may
reuse the common grammar in section 4 with a different kind, as recorded in
ADR-007.
3. Format overview
A manifest is a line-oriented text file. It is project-owned and section-based, closer to INI than to JSON or YAML. The project chose this shape so the frontend can parse it with a small bounded grammar rather than adding a general data language dependency to C frontend code.
A pet presentation manifest is stored as assets/pet/manifest.petasset.
4. Common manifest grammar
The grammar in this section is shared by every manifest kind. A specific schema, such as the pet presentation schema in section 5, defines which sections and directives are valid for its kind.
4.1 Line structure
The file is ASCII text. Each line is either the header line, a section header, a comment, a blank line, or a directive line. Fields on a line are separated by one or more ASCII space characters, and leading and trailing spaces on a line are ignored.
A line ends with a line feed, and the last line of a file needs no terminator. One carriage return
immediately before a line feed belongs to the terminator, so a checkout that rewrote the line endings
still describes the same assets. Every other byte on a line is a printable ASCII character between
0x20 and 0x7E. The space is the only separator, so a tab, a carriage return anywhere else, and
any other control or non-ASCII byte is invalid rather than treated as whitespace.
4.2 Header line
The first line that is neither blank nor a comment must be the header line:
manifest <kind> <version><kind> follows the identifier rules in section 6 and names the schema the rest of the file uses.
<version> is a decimal unsigned integer that selects the schema version for that kind. The header
carries no other fields.
A pet presentation manifest begins with:
manifest pet 1This document accepts only kind pet and only version 1. A different kind or version is a different
contract. A future UI manifest may begin with manifest ui 1 and reuse this grammar, but it defines
its own sections and must not be folded into the pet manifest.
4.3 Sections
After the header, content is grouped into sections. A section header is a section name enclosed in square brackets on its own line:
[images]Each schema defines its required sections, their required order, and the directive form inside each section. A section name that the active schema does not define is invalid. A directive line inside a section that does not match the section’s defined form is invalid, including a line that carries extra fields.
4.4 Comments and blank lines
A full-line comment begins with # as the first non-space character and runs to the end of the line.
Inline comments are not supported, so # does not begin a comment part way through a directive line.
Blank lines are allowed anywhere after the header line.
5. Pet presentation schema v1
A manifest pet 1 file contains exactly these four sections, in this order, and all four are
required:
[images][animations][roles][fallback]
5.1 Images
Each directive names an image identifier and the relative path of its PNG file:
<image-id> <relative-png-path>The identifier follows section 6. The path follows section 7.
5.2 Animations
Each directive names an animation identifier followed by one or more frames. A frame is an image
identifier already defined in [images] paired with a duration in milliseconds:
<animation-id> (<image-id> <duration-ms>)+A single-frame animation is one image and one duration. A multi-frame animation lists further image
and duration pairs on the same line. <duration-ms> is a decimal unsigned integer between 1 and
60000 inclusive. Every referenced image identifier must resolve to an [images] entry.
5.3 Roles
Each directive maps a presentation role identifier to an animation identifier already defined in
[animations]:
<role-id> <animation-id>Presentation roles are frontend presentation vocabulary derived from a PetSnapshot. They are not
Core authoritative state. The recommended initial role names are activity_idle, activity_curious,
activity_attentive, expression_neutral, expression_happy, and fallback_default.
5.4 Fallback
The section contains exactly one role identifier already defined in [roles]:
<role-id>The fallback role is the calm presentation used when a presentable Core state has no mapped role, as described in section 9.
6. Identifiers
An identifier uses lowercase ASCII letters, digits, and the underscore character. It must start with a lowercase ASCII letter and is at most 31 bytes long.
The following reserved words are the structural keywords of the grammar and cannot be used as an image, animation, or role identifier:
manifestimagesanimationsrolesfallback
7. Paths
A path in the manifest is relative to the asset set that owns the manifest. For the pet manifest that
root is assets/pet/, so sprites/idle.png names assets/pet/sprites/idle.png.
A path is at most 255 bytes long, uses the forward slash as its only separator, and ends in .png. To
keep runtime resolution predictable and safe, the following are invalid:
- an absolute path
- a
..path segment - an empty path segment
- a backslash
- a colon, which is how a Windows drive prefix is written
- a final segment of
.pngalone, which names no file
The manifest must not name a file outside its asset set. Pet presentation images live under
assets/pet/sprites/.
8. Limits
The manifest and the records derived from it are bounded so that parsing work and stored records stay bounded in the frontend. The version 1 limits are:
| Limit | Value |
|---|---|
| Manifest file size | 16 KiB |
| Line length | 384 bytes |
| Images | 32 |
| Animations | 32 |
| Roles | 64 |
| Total frames | 128 |
| Frames per animation | 16 |
| Frame duration | 1 to 60000 milliseconds |
| Identifier length | 31 bytes |
| Path length | 255 bytes |
Several roles may select the same animation, so roles are bounded separately and more loosely than animations. The frame limit is a pool shared by every animation, so animations that each stay inside the per-animation limit can still ask for more frames than the manifest may hold.
9. Fallback reference chain
The fallback role must resolve through the whole chain so that a calm presentation is always
available. The fallback role in [fallback] resolves through [roles] to an animation. That
animation resolves through [animations] to one or more frames. Each frame resolves through
[images] to a PNG path.
[fallback] role -> [roles] animation -> [animations] frames -> [images] pngThis chain is why an asset set is accepted whole or not at all. A manifest that is missing, invalid,
or unreadable leaves the chain without a source, and so does an accepted manifest naming an image the
asset set cannot reach or that does not begin with the PNG signature. In each case the shared asset
layer reports that the built-in fallback is required instead of offering the records it did read. The
signature check reads the leading bytes of each file and decodes nothing, so an empty file, a text
file carrying a .png name, and a directory a reader can open are all rejected, while a file that
decodes badly is still the frontend’s failure to handle. That built-in presentation is a calm resting
colour and one frame duration, with no text, glyph, or warning colour, and it is the same whatever
failed. Drawing it belongs to the frontend.
10. Example
manifest pet 1
# pet presentation assets for v0.2.0
[images]
sprite_fallback sprites/fallback.png
sprite_idle sprites/idle.png
sprite_curious sprites/curious.png
sprite_attentive sprites/attentive.png
sprite_happy sprites/happy.png
[animations]
anim_fallback sprite_fallback 1000
anim_idle sprite_idle 900
anim_curious sprite_curious 700
anim_attentive sprite_attentive 700
anim_happy sprite_happy 300 sprite_idle 300
[roles]
activity_idle anim_idle
activity_curious anim_curious
activity_attentive anim_attentive
expression_neutral anim_idle
expression_happy anim_happy
fallback_default anim_fallback
[fallback]
fallback_defaultIn this example the fallback chain resolves as fallback_default to anim_fallback to
sprite_fallback to sprites/fallback.png. The anim_happy entry shows a two-frame animation, and
every other animation is a single frame.
11. Runtime asset layout
Canonical source assets are the source of truth and are stored once:
assets/pet/manifest.petasset
assets/pet/sprites/*.pngA development build produces a runtime copy next to the selected build output. A gate composition build uses the isolated composition directory:
build/<preset>-<composition>/assets/pet/manifest.petasset
build/<preset>-<composition>/assets/pet/sprites/*.pngA direct CMake preset build uses the preset directory:
build/<preset>/assets/pet/manifest.petasset
build/<preset>/assets/pet/sprites/*.pngThe runtime copy is a generated artefact. It is not canonical and must not be edited as source. The runtime asset layout is relative to the build output and must not depend on the source checkout path, so a runtime asset is found the same way regardless of where the repository is checked out.
The shared asset-set layer resolves this runtime root and reads files through a reader contract rather than assuming that every consumer has a desktop filesystem. The development desktop reader uses the layout above, while tests can provide manifest bytes directly and future embedded frontends can read from generated arrays, flash storage, or packed binary assets without changing the manifest parser.
The copy is produced by the pet_runtime_assets build target and is verified by one test that runs
from the build directory. That test loads the copied manifest through the desktop reader and checks
that every image it names can be opened there, so a canonical asset that was renamed or left out of
the copy fails the build rather than the first run.
12. Validation expectations
The bounded parser validates a manifest against this contract and rejects an invalid manifest rather
than partially applying it. It is a shared component under components/manifest rather than frontend
private code, because the grammar in section 4 is common to future manifest kinds. pet_manifest_parse
reads manifest text from memory into caller-owned fixed-capacity storage and returns a
PetManifestStatus naming the first rule the text breaks. A rejected manifest leaves that storage
empty. A rejected manifest includes any of the following:
- a header line that is missing, that names a kind other than
pet, or that names a version other than1 - a section name the schema does not define
- a required section that is missing or out of order
- a directive line whose form does not match its section, including extra fields
- an identifier that breaks the rules in section 6 or reuses a reserved word
- a duplicate image, animation, or role identifier
- a role, animation, or image reference that does not resolve
- a
[fallback]section that does not contain exactly one role identifier - a path that breaks the rules in section 7
- a frame duration outside 1 to 60000 milliseconds
- a manifest, line, count, or length that exceeds a limit in section 8
13. Out of scope for PBI-048
This contract defines the schema and layout. The following remain out of scope for the PBI that introduces this document and are implemented by later PBIs:
- the guaranteed fallback presentation rendering, in PBI-054
- the SDL image loading from accepted frontend-neutral asset records, in PBI-051
- the presentation role mapping and animation frame selection, in PBI-052 and PBI-053
- any UI, menu, or application chrome manifest, which is a separate future asset layer
14. References
- Architecture for v0.2.0, sections 5.5, 7, and 8.3
- ADR-007
- Product specification,
SPEC-FR-011,SPEC-FR-025,SPEC-FR-029,SPEC-NFR-004,SPEC-NFR-006 - Epics,
E12 - PBIs,
PBI-048,PBI-049,PBI-050,PBI-051