Pet asset manifest v1

Document versionDateSummary
v12026-07-21Define the common manifest grammar, the pet presentation schema v1, and the development runtime asset layout
v22026-07-21Record the role limit, the line terminator and byte rules, and the implemented parser component
v32026-07-21Record the reusable asset-set layer and reader boundary
v42026-07-21Record 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 1

This 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:

  1. [images]
  2. [animations]
  3. [roles]
  4. [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:

  • manifest
  • images
  • animations
  • roles
  • fallback

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 .png alone, 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:

LimitValue
Manifest file size16 KiB
Line length384 bytes
Images32
Animations32
Roles64
Total frames128
Frames per animation16
Frame duration1 to 60000 milliseconds
Identifier length31 bytes
Path length255 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] png

This 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_default

In 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/*.png

A 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/*.png

A direct CMake preset build uses the preset directory:

build/<preset>/assets/pet/manifest.petasset
build/<preset>/assets/pet/sprites/*.png

The 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 than 1
  • 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