# LCD Expressions This document describes how LCD expressions are owned, requested, played, and extended. ## Ownership Model LCD expressions are split into semantic intent and low-level playback: - `control_plane` decides when an expression should be requested. - `domain` owns semantic expression IDs and maps them to playback requests. - `platform` owns LCD presentation primitives and direct ST7789 drawing. Do not bypass this path from upper layers. The expected flow is: ```text control_plane condition -> domain_runtime_request_expression() -> platform_lcd_request_presentation() -> LCD direct render task ``` `control_plane` must not call `platform_lcd_*` APIs directly. `platform` must not expose domain expression IDs. ## Current Backend State The current LCD backend is `components/platform/src/lcd_direct.c`. It initializes the 240x320 ST7789P3 panel directly through SPI using the `CONFIG_TQ_SCREEN_*` pin and orientation settings, starts one render task, and accepts the existing platform presentation requests. The 2026-07-24 board uses MOSI GPIO18, clock GPIO8, CS GPIO15, D/C GPIO16, reset GPIO17, and backlight GPIO7. The default X/Y swap exposes a 320x240 landscape drawing surface so the existing LCD assets retain their current geometry. The direct backend currently draws deterministic full-screen test patterns for presentation requests. Real image/resource playback is the next migration step. The previous widget-based display source files, display middleware dependency, and legacy animation assets have been removed from the build. ## Current Expressions | Domain expression | Trigger owner | Playback kind | Current behavior | | --- | --- | --- | --- | | `DOMAIN_EXPRESSION_IDLE` | LCD startup / restore path | `PLATFORM_LCD_PRESENTATION_IDLE` | Draws the direct backend idle test pattern. | | `DOMAIN_EXPRESSION_SERVER_ERROR` | Multimodal server failure events | `PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE` | Draws a direct backend pattern selected from the mapped asset name. | | `DOMAIN_EXPRESSION_SLOT_REELS` | Slot feature switch worker | `PLATFORM_LCD_PRESENTATION_SLOT_REELS` | Draws a direct backend slot-pattern placeholder. | | `DOMAIN_EXPRESSION_WIFI_DISCONNECTED` | Wi-Fi state monitor worker | `PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE` | Draws a direct backend pattern selected from the mapped asset name. | | `DOMAIN_EXPRESSION_WEALTH_SPIN` | Model-command direction worker | `PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE` | Draws a direct backend pattern selected from `spin`. | | `DOMAIN_EXPRESSION_WEALTH_DIRECTION` | Model-command direction worker | `PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE` | Draws a direct backend pattern selected from the mapped direction asset name. | | `DOMAIN_EXPRESSION_OTA_UPDATE` | OTA update worker | `PLATFORM_LCD_PRESENTATION_ASSET_SEQUENCE` | Loops the three-frame `ota` sequence during firmware download and installation. | Server error currently triggers only from: - `CONTROL_PLANE_MULTIMODAL_EVENT_TASK_FAILED` - `CONTROL_PLANE_MULTIMODAL_EVENT_OUTPUT_ERROR` Server error expression requests are suppressed while Wi-Fi STA is disconnected; the Wi-Fi disconnected expression owns that state because server reachability is not meaningful without Wi-Fi connectivity. Transport disconnect/error events are not server error expressions. ## Asset Migration Direction The image playback path is a platform-owned direct LCD asset pipeline. Runtime PNG files are stored in the `gifpack` LittleFS partition, decoded through `lodepng`, converted to RGB565 in PSRAM, and written to the ST7789 panel through the existing bounded row-transfer buffer. - PNG decode, the bounded four-entry LRU cache, and ST7789 writes stay inside `platform`. - Domain continues to map semantic expressions to short asset IDs or names. - Upper layers continue to use `domain_runtime_request_expression()`. - Runtime LCD assets must be added to the explicit PNG allowlist in `components/platform/CMakeLists.txt`; files that only exist in the source directory are not packaged automatically. ## Requesting Expressions Use `domain_runtime_request_expression()` from orchestration code: ```c const domain_expression_request_t request = { .id = DOMAIN_EXPRESSION_SERVER_ERROR, .trigger = DOMAIN_EXPRESSION_TRIGGER_SERVER_TASK_FAILED, }; (void)domain_runtime_request_expression(&request); ``` Trigger IDs should describe why the expression was requested. They are useful for logs and future policy decisions, but the playback mapping is still owned by the domain catalog. ## Playback Scheduling The platform LCD presentation scheduler is non-blocking: - Requests are copied at API entry. - Panel drawing is serialized by the LCD render task. - Same active or pending requests are coalesced. - Higher-priority interrupting requests replace the active presentation. - Lower-priority requests are ignored while a higher-priority presentation is active or pending. Current priorities: | Presentation | Priority | Interrupts current | | --- | --- | --- | | Wi-Fi disconnected / idle restore | `120` | Idle restore interrupts Wi-Fi disconnected. | | Wealth spin / wealth-direction reveal | `110` | Yes | | Server error | `100` | Yes | | Slot reels | `20` | No | ## Adding a New Expression Follow this checklist for every new expression: 1. Add a semantic ID to `domain_expression_id_t` in `components/domain/include/domain.h`. 2. Add a trigger reason to `domain_expression_trigger_t` if the new expression needs a new trigger source. 3. Add a catalog entry in `components/domain/src/runtime/domain_runtime_adapters.c`. 4. Add or map the platform direct LCD asset in the platform LCD backend. 5. Add or update the `control_plane` rule that creates `domain_expression_request_t`. 6. Build with the repository-approved ESP-IDF path and verify LCD logs on hardware. Do not add one-off APIs such as `domain_runtime_show_happy()` or `platform_lcd_show_error()`. Keep extension through the expression catalog and the unified presentation request path. ## Validation Checklist Before merging expression changes: - Run `idf.py build` through the repository-approved ESP-IDF activation path. - Verify `control_plane` does not include `platform.h` or call `platform_lcd_*`. - Verify platform code does not refer to `DOMAIN_EXPRESSION_*`. - For finite asset sequences, verify completion returns to idle when `return_to_idle` is set. - For interrupting expressions, verify the active lower-priority presentation is cancelled cleanly.