Last development

Task

PBI-122, which puts the pack in the firmware image and the canonical pet on the panel. It completes E35, so the only open epics of v0.5.0 are E36 and E37.

Changed areas

New frontends/tft/src/sprite.{c,h}, frontends/tft/tests/test_sprite.c, apps/firmware/include/pet_app_firmware/assets.h, apps/firmware/src/assets.c, apps/firmware/tests/test_assets.c, and docs/adr/adr-020-pack-frame-composition-and-whole-scale-placement.md.

Changed components/layout/include/pet_layout/layout.h, src/layout.c, and its test, components/assets/src/pack_manifest.c, the TFT frontend, render, test, runner, suite header, CMake, and README, the firmware diagnostics, tests, runner, suite header, CMake, and README, config/verification.json, platforms/esp_idf/CMakeLists.txt, main/CMakeLists.txt, and main/app_main.c. Updated the architecture, the PBI, the epic, the limitation register, and the progress record.

The untracked README.md, DEVELOPMENT.md, and PRODUCT-VARIANTS-DISCUSSION.md at the repository root belong to the maintainer and were not touched.

What changed

The generated pack is embedded by target_add_binary_data in the main component, because a target consumes a generated file only in the directory that names it, and the generation target is defined after the components are registered. The application reaches the bytes through PetFirmwareAssetSource, a seam shaped like PetFirmwareHeap, and pet_firmware_assets_open validates them and materialises the manifest into caller-owned storage. app_main holds that storage statically and emits pack-ready with the version, the entry count, and the length, or pack-rejected with the acceptance rule the pack broke.

components/layout gained pet_layout_fit_whole, which places a canvas at the largest whole multiple both extents allow and refuses one the output cannot take even once. The pack path uses it and the procedural fallback keeps the filling pet_layout_fit, so the 64 by 64 sprites land at 128 by 128 on the panel with every sprite pixel covering the same square. ADR-020 records that decision, the surround, and the alternatives.

pet_frontend_tft_sprite_draw reads the colour payload and the one-bit mask in place and writes only the opaque pixels, so the surround shows through wherever a frame is transparent. pet_frontend_tft_compose_frame clears the surface with the role’s letterbox colour and draws the frame on it. Every arm of the resolution chain that cannot produce a frame, which is no pack, no selection, no entry, and a geometry the surface cannot take whole, composes the procedural appearance instead.

One defect was found on the board and fixed. pet_assets_pack_manifest cleared its destination by assigning a PET_MANIFEST_EMPTY automatic, which is 13844 bytes against a 3584-byte main task stack, so the first call overflowed the stack and the board panicked in the tick handler and rebooted in a loop. It clears in place now, and architecture section 8.8 records the rule.

Tests and checks

./gate.py fast --all-compositions
./gate.py strict --all-compositions
./gate.py audit --all-compositions
python3 tools/verify/embedded.py --require
python3 tools/verify/boundaries.py
every suite under tools/tests
idf.py build

All passed. pet_layout_tests grew from 10 to 16, pet_frontend_tft_tests from 52 to 63, and pet_firmware_tests from 51 to 57. The new sprite suite covers the mask bit order, the row stride, a clipped placement, a scale of one, and every refused geometry and payload. The frontend suite asserts that the pack’s own bytes reach the frame buffer and that a frame the surface cannot take whole composes the same bytes as the procedural appearance. The firmware suite covers the pack seam over an accepted pack, a damaged one, a platform with no pack, and a refusing source.

idf.py build succeeded and the image grew from 0x2d390 to 0x388f0 bytes, which is the 44232-byte pack plus the sprite and seam code, with the partition 78 per cent free.

Device evidence, on the classic board over /dev/ttyUSB0. The accepted run reported pet event=pack-ready version=1 entries=5 bytes=44232 and the maintainer observed the canonical pet on the panel. A pack with one payload byte flipped reported pet event=pack-rejected reason=check, the run continued with no reset, and the maintainer observed the procedural fallback, which is a dark surround with a lighter body. The good pack hashes to a627c8e7368dd3a3470ca42f174a54fb47cdc718721c2cf338ef939d2b9dac9e, the value PBI-118 recorded.

Component files updated

No component knowledge files required updates.

Known limitations

LIM-013 stays open until PBI-128 closes it, which is the release-level review, and its closing evidence now exists. LIM-012 narrowed to say the panel shows the canonical pet. LIM-020 is unchanged.

Suggested commits

1. Whole-multiple placement

git add components/layout/include/pet_layout/layout.h components/layout/src/layout.c components/layout/tests/test_pet_layout_fit.c

feat(layout): place a canvas at the largest whole multiple

2. Pack frame composition

git add frontends/tft/src/sprite.c frontends/tft/src/sprite.h frontends/tft/src/render.c frontends/tft/src/render.h frontends/tft/src/frontend.c frontends/tft/tests/test_sprite.c frontends/tft/tests/test_frontend.c frontends/tft/tests/test_runner.c frontends/tft/tests/test_suites.h frontends/tft/CMakeLists.txt frontends/tft/README.md

feat(tft): compose a pack frame through its colour payload and its mask

3. Manifest materialisation stack cost

git add components/assets/src/pack_manifest.c

fix(assets): clear the materialised manifest in place

4. Firmware pack seam and diagnostics

git add apps/firmware/include/pet_app_firmware/assets.h apps/firmware/include/pet_app_firmware/diagnostics.h apps/firmware/src/assets.c apps/firmware/src/diagnostics.c apps/firmware/tests/test_assets.c apps/firmware/tests/test_diagnostics.c apps/firmware/tests/test_runner.c apps/firmware/tests/test_suites.h apps/firmware/CMakeLists.txt apps/firmware/README.md

feat(firmware): open the embedded pack through a platform seam

5. Embedded pack in the image

git add platforms/esp_idf/CMakeLists.txt platforms/esp_idf/main/CMakeLists.txt platforms/esp_idf/main/app_main.c config/verification.json

feat: embed the asset pack in the firmware image

Suggested next step

E36, starting with PBI-123, the pure button filter. It needs no board until PBI-124 adds the GPIO backend, and PBI-125 is where a filtered edge becomes a semantic action. PBI-126 then runs the release gates over the whole set, and it now depends on nothing that is still open in E35.