Project progress
| Document version | Date | Summary |
|---|---|---|
| v1 | 2026-07-18 | Initial progress record |
| v2 | 2026-07-20 | Record completion of E08 architecture planning |
| v3 | 2026-07-24 | Archive the completed v0.2.0 progress and clear the active table |
| v4 | 2026-07-27 | Record completed v0.3.0 work through E20 |
| v5 | 2026-07-27 | Refine E20 around the T-Display family and selectable board variants |
| v6 | 2026-07-28 | Record E21 component selection and desktop reader isolation |
| v7 | 2026-07-28 | Record E21 completion after the embedded and probe coverage boundaries |
| v8 | 2026-07-28 | Record E22 completion after the greet presentation rule |
| v9 | 2026-07-28 | Record compact documentation portal route titles |
| v10 | 2026-08-01 | Archive the completed v0.3.0 progress and clear the active table |
| v11 | 2026-08-04 | Archive the completed v0.4.0 progress and clear the active table |
This append-only table records completed project tasks. Add one concise row after a task satisfies its acceptance criteria. Do not record incomplete work as progress.
Completed v0.1.0 progress is archived in release v0.1.0.
Completed v0.2.0 progress is archived in release v0.2.0.
Completed v0.3.0 progress is archived in release v0.3.0.
Completed v0.4.0 progress is archived in release v0.4.0.
The table below records work after v0.4.0.
| Date | PBI or task | Outcome | Verification | Notes |
|---|---|---|---|---|
| 2026-08-06 | E30, PBI-103, PBI-104, PBI-105 | Completed the v0.5.0 architecture acceptance by naming the ADR owners, recording the serial diagnostic vocabulary and the device evidence model, and accepting ADR-016 for the hosted firmware-tft composition | Documentation-only task with no compiler check, reviewed against the milestone entry, the epic map, the arc42 policy, and the composition, gate, and verification configuration it describes | The vocabulary is ten contractual events, the soak is two hours with a heap reading at each end, and the embedded check’s display family rule and subject set are named as tooling the backend PBIs must move |
| 2026-08-06 | PBI-106 | Added ./pet.py flash and ./pet.py monitor, which delegate to idf.py from platforms/esp_idf with an optional --port and name an inactive SDK environment or an absent port before running anything | python3 tools/tests/test_pet.py, ./gate.py strict --all-compositions, and both commands run against the classic board on /dev/ttyUSB0 with ESP-IDF v6.0.2 active | esp-idf-monitor.cfg moves the monitor exit key to Ctrl-C without entrypoint code. monitor needs an interactive terminal because ESP-IDF requires a TTY on standard input, which section 3.6 records. The run proves the command path and is not one of the release’s three device observations |
| 2026-08-06 | PBI-107 | Lit the classic T-Display panel from the project firmware over esp_lcd and transferred a diagnostic pattern of a one pixel border, four coloured corner markers, and three primary colour bars in one full-frame draw, with every SDK call inside platforms/esp_idf/main/display.c and the panel events emitted in the contractual line format | idf.py build, ./pet.py flash to the board on /dev/ttyUSB0, the maintainer’s observation of the pattern on the glass, a forced panel failure that produced pet event=display-failed reason=panel-io without a reset loop, ./gate.py strict --all-compositions, and the embedded and include boundary checks | The first physical output the project has produced. The bring-up path is deliberately platform-owned and moves into the TFT display backend at PBI-116. Its sources sit outside the directories the format and boundary checks scan |
| 2026-08-06 | PBI-108 | Recorded the measured classic panel profile with per-value provenance, which is a native portrait 135 by 240 panel transferred as landscape 240 by 135 with swapped axes, a mirrored x axis, offsets 40 and 52, RGB element order, an inverted colour path, and RGB565 pixels sent high byte first | The maintainer’s observation of the bring-up pattern on the board, a reflash that reproduced the recorded values, and ./gate.py strict --all-compositions | The blanket unverified comment is replaced by measured, exercised, and unexercised groups. Buttons, flash size, and PSRAM stay vendor-documented, and LIM-012 narrowed to the panel path alone |
| 2026-08-07 | PBI-109 | Implemented the serial diagnostic emitter in apps/firmware, which owns the prefix, the event vocabulary, the reason values, and the line maximum, and replaced the ESP-IDF entry’s inline display prints and its two free-form lifecycle lines with boot, display-ready, display-failed, and runtime-fault | ./gate.py strict --all-compositions, the format, cppcheck, and include boundary checks, idf.py build, python3 tools/verify/embedded.py --require with the emitter added to the required embedded sources, and ten diagnostics tests compiled and run by hand against the pinned cmocka, all passed. The flashed board printed pet event=boot board=t-display-classic target=esp32 version=0.4.0 and pet event=display-ready width=240 height=135 rotation=90 colour-order=rgb, which completed E31 | A successful bounded lifecycle now prints nothing, because the vocabulary has no event for one and runtime-ready belongs to PBI-111. pet_firmware_tests is registered but reaches the gate only when PBI-113 registers the composition, whose acceptance criteria now name it |
| 2026-08-07 | PBI-110, PBI-111 | Turned the firmware into a continuous runtime. Time reaches the application through a platform seam, every delta is derived from the previous monotonic reading and clamped to 1000 ms with a time-clamped report, and one world now lives across a stepped loop that emits runtime-ready once, yields between iterations, and does not return | ./gate.py audit --all-compositions, the format, cppcheck, include boundary, and --require embedded checks, idf.py build, the tooling suites, and 33 firmware tests compiled and run by hand, all passed. The flashed board emitted pet event=runtime-ready tick-ms=33 and then ran 39 seconds with no reset, no watchdog abort, and no return from app_main | ADR-017 records the policy, the 1000 ms bound, and its alternatives. pet_firmware_run is gone and its Core-suite tests moved into apps/firmware/tests, so no hosted composition compiles the app until PBI-113 registers one. PBI-112 measures the real pace the bound was chosen against |
| 2026-08-07 | PBI-113, PBI-114 | Registered firmware-tft in the composition registry, which puts the firmware application and its 33 hand-compiled tests inside the gate, and gave frontends/tft its public contract and a clipped frame buffer surface over caller-owned RGB565 bytes | ./gate.py audit --all-compositions over three compositions, the format, cppcheck, include boundary, and embedded checks, and the composition, gate, project command, and verification policy suites, all passed. pet_frontend_tft_tests adds 28 tests and both new suites are configured, run, and reported by CTest | The firmware_includes helper in gate.py became board_includes, because only a board now sits outside every hosted composition. ADR-018 records the full frame buffer strategy, its 64800-byte cost, and the streaming and dirty-region alternatives |
| 2026-08-07 | PBI-115 | Turned the TFT surface into a composition. A snapshot maps to a presentation role through components/presentation, the canvas placement comes from pet_layout_fit, and the procedural fallback draws a letterbox, a canvas, and a body whose tone and size differ per role, so a world that changes state changes the glass without any asset | ./gate.py audit --all-compositions, ./gate.py strict --app firmware, the format, cppcheck, include boundary, and embedded checks, and the verification, composition, gate, and project command suites, all passed. pet_frontend_tft_tests grew from 28 to 47 tests across the new fallback, render, and present suites | The composed frame is a function of the snapshot alone, so animation time and the canvas extents both wait for the pack and are recorded as PBI-121 work. The role-varying body is what lets PBI-116 observe presentation progress on the panel before any pack exists |
| 2026-08-07 | PBI-116, E33 | Put the pet on the glass. The panel bring-up, the DMA frame buffer, and the transfer moved out of the diagnostic path into frontends/tft/src/esp_idf/display_backend.c behind a build-selected backend contract, the application presents through a PetFirmwareDisplay seam that transfers one full frame per changed frame and nothing for an unchanged one, and a refused transfer becomes a runtime-fault rather than an abort | ./gate.py audit --all-compositions, the format, cppcheck, include boundary, and --require embedded checks, idf.py build, and the tooling suites, all passed. pet_firmware_tests gained eight tests over the seam and the presentation rule and the verification policy suite gained four. On the board, pet event=display-ready width=240 height=135 rotation=90 colour-order=rgb matched the measured profile and the maintainer observed the pet through the fallback appearance, which is a lighter body centred on a canvas that fills the panel height with a letterbox band at each side | The frame buffer belongs to the backend rather than the frontend, because esp_lcd transfers by DMA, and architecture section 6.1 records that difference from section 8.8. embedded_build.sdk_include_subjects is new policy that names the sources allowed to reach their own SDK, and the backend still carries -std=c99 and -Werror. Without a button the world reaches curious about a minute after boot and no time row leaves it, so the observed progress is one change that then held for the whole soak, and richer progress waits for PBI-125 |
| 2026-08-07 | PBI-112, E32, BUG-001 | Observed the two-hour continuous device run on the physical classic board, which is the first evidence that the runtime lives rather than boots. The run reached uptime-s=7416, 2 hours 3 minutes 36 seconds, with 104 heap readings and no reset, no watchdog abort, no runtime-fault, no time-clamped, and no return from app_main. free-bytes and min-free-bytes were both 237104 at the first reading and at the last, unchanged across the whole run, and every one of the 103 gaps between readings was exactly 72 seconds, which is the announced 40 millisecond iteration over 1800 of them | The captured transcript, kept at _report/soak-transcript.txt, read against the thirteen cases in the soak procedure, plus the maintainer’s own observation of the panel across the run | The run also answers the long-run panel half of PBI-116, because the pet was on the glass while the transcript recorded the heap trend. BUG-001 was found on the first device run and fixed before this one, so the announced pace and the measured pace now agree. The image was the working tree over 6c6e1e5, and the transcript reports version=0.4.0 because that is the project version until v0.5.0 is released, so the observation names the commit |
| 2026-08-07 | PBI-117 | Decided and recorded the embedded asset pack format field by field, which is a flat little-endian artefact holding a 28-byte header, the compiled presentation tables, an entry table indexed by image, the colour and mask payloads, and a trailing CRC-32. ADR-019 carries the decision and its rejected alternatives, the pack contract carries the layout, the acceptance rules, and the converter contract, and the save format gained a contract document of its own so both binary artefacts are described the same way | Documentation-only task with no compiler check, reviewed against the manifest contract, the PetManifest and presentation structures the reader must materialise, the measured classic panel path, and sections 5.5, 5.7, 8.3, and 8.4 of the architecture. The worked size example was computed from the canonical asset set, which is five 64 by 64 RGBA sprites | Pixels are RGB565 high byte first while every other field is little-endian, which is declared in the header rather than left implicit, because that order is what the ST7789 consumes. Four-byte alignment is part of the format because the reader hands out pointers into the pack. The one-bit mask enters now although PBI-121 is its first consumer, since adding it later would be a version change |
| 2026-08-08 | PBI-118 | Implemented the standard-library-only deterministic converter, with the manifest grammar isolated from pack encoding, bounded PNG decoding, RGB565 and mask conversion, field-by-field pack encoding, and atomic output replacement | python3 tools/tests/test_pack.py, every existing tooling suite, and ./gate.py fast --all-compositions passed, and the canonical set produces 44232 bytes with SHA-256 a627c8e7368dd3a3470ca42f174a54fb47cdc718721c2cf338ef939d2b9dac9e across repeated runs | The grammar suite mirrors the C parser’s acceptance, rejection, and limit cases without constructing a pack, and LIM-019 records the duplicate reader with the first grammar change as the trigger for ADR-015 |
| 2026-08-08 | PBI-119, E34 | Added the shared pet_asset_pack CMake target to hosted and ESP-IDF builds, generating assets/pet.pack below the build directory from the canonical manifest, every canonical PNG, and both converter modules | The CMake integration tests proved clean generation before a consumer, complete input dependencies, regeneration after a changed manifest or PNG, and no tracked pack, while ./gate.py fast --all-compositions generated the same 44232-byte pack in all three hosted compositions | The strict gate stopped at the existing desktop ASan tests because LeakSanitizer cannot run under ptrace, and the ESP-IDF boundary check passed over its existing build while idf.py build was unavailable without an active ESP-IDF environment |
| 2026-08-12 | PBI-120 | Added the bounded embedded pack reader to components/assets. pet_assets_pack_open validates a whole pack against the sixteen acceptance rules of the version 1 contract before answering anything, records the broken rule in one of thirteen reason values, and hands out pointers into the borrowed bytes rather than copies. PET_ASSETS_PACK_READER selects it, and a test build is required to take it because the hosted gate is the only place the format is verified | ./gate.py fast, strict, and audit, each --all-compositions, all passed, with audit run from a cleaned tree, and idf.py build passed with the reader compiled into the ESP-IDF build. pet_assets_tests grew from 20 to 49 tests and pet_assets_runtime_tests from 2 to 5, the latter reading the generated 44232-byte pack from the build directory | The pack tests carry the contract table by hand rather than the reader’s own constants, and reseal the check after each mutation so a case reaches its own rule. LIM-020 records the second bitwise CRC-32, since a component does not depend on the Core. The PBI gained the reason vocabulary, the fixture decision, and a corrected ownership criterion, because the reader necessarily borrows the pack bytes |
| 2026-08-12 | PBI-121 | Turned a pack into the presentation structure the desktop parse produces, and gave the TFT frontend its canvas and its animation time. pet_assets_pack_manifest fills a caller-owned PetManifest with image indices where the text carried paths, the frontend takes the canvas from the extent of the image the selection resolves to rather than from a default it stated itself, and the animation time arrives from the monotonic reading the firmware step already took | ./gate.py fast, strict, and audit, each --all-compositions, and idf.py build, all passed. pet_assets_tests grew from 49 to 54, pet_assets_runtime_tests from 5 to 7, and pet_frontend_tft_tests from 47 to 52. The runtime suite resolves all six canonical roles at six animation times through both structures and compares seven fields each time | PET_FRONTEND_TFT_CANVAS_WIDTH and its height are gone, and a frontend with no assets composes on the whole surface, so the device fallback now fills the panel rather than sitting in a 64 by 64 letterbox. The materialised structure is 13844 bytes on the esp32 toolchain, measured by compiling the header with xtensa-esp32-elf-gcc, and architecture section 8.8 records it. The pack test builder moved to a shared double, because the frontend cases need a pack and a second hand-written contract table would drift |
| 2026-08-18 | PBI-122, E35 | Put the canonical pet on the panel. The generated pack is embedded in the firmware image as read-only bytes, the application opens it through a platform seam into static storage and reports pack-ready or pack-rejected, and the frontend draws a frame from its colour payload and its one-bit mask at the largest whole multiple pet_layout_fit_whole allows, falling back to the procedural appearance on every arm of the resolution chain that cannot produce one | ./gate.py fast, strict, and audit, each --all-compositions, the embedded, boundary, and tooling checks, and idf.py build, all passed, with pet_layout_tests at 16, pet_frontend_tft_tests at 63, and pet_firmware_tests at 57. On the board pet event=pack-ready version=1 entries=5 bytes=44232 accompanied the maintainer’s observation of the canonical pet, and a pack with one flipped byte reported pet event=pack-rejected reason=check and kept running on the observed fallback | ADR-020 records the whole-multiple placement and the surround a transparent mask bit shows. The first device run panicked because pet_assets_pack_manifest cleared a 13844-byte automatic against a 3584-byte main task stack, which is fixed and recorded in architecture section 8.8 |