diff --git a/docs/plans/2026-07-12-001-feature-pixel-pet-plan.md b/docs/plans/2026-07-12-001-feature-pixel-pet-plan.md new file mode 100644 index 0000000..ccc9892 --- /dev/null +++ b/docs/plans/2026-07-12-001-feature-pixel-pet-plan.md @@ -0,0 +1,200 @@ +--- +title: Pixel Pet - Plan +type: feat +date: 2026-07-12 +topic: pixel-pet +artifact_contract: ce-unified-plan/v1 +artifact_readiness: requirements-only +product_contract_source: ce-brainstorm +execution: code +--- + +## Goal Capsule + +| Field | Value | +|---|---| +| **Objective** | Add a passive ambient "pixel pet" — an abstract blob creature — to the bedside clock's UI that reflects four environmental signals (time of day, outdoor weather, indoor T/RH, music playback) without adding interaction burden. | +| **Product authority** | SlumberCube (bedside clock product line, single-user device on ESP32-C3 firmware). | +| **Execution profile** | `code` — firmware feature on the existing main MCU. No new product line, no cloud dependency, no companion app. | +| **Stop conditions** | Pet renders on the existing canvas; behavior is purely passive; night mode hides it; pet resets on each wake. No new screens, no new buttons, no audio output from the pet. | +| **Tail ownership** | Pet lives in `clock_screen.c` (or a small new module pulled into the same draw loop); evolves when product requirements change. | + +## Product Contract + +### Summary + +A passive abstract blob creature lives in the bottom-right 24×24 pixels of the clock screen, alongside the weather panel. Four environmental signals — time of day, outdoor weather, indoor T/RH, and music playback — each toggle an internal property layer (shape, eye state, surface texture, motion mode) on top of an always-running idle animation. Reactions are coherent within a wake cycle and reset on deep sleep; the pet hides during night mode and never reacts to user input. + +### Problem Frame + +SlumberCube today is purely informational: clock digits, weather text, indoor temperature, forecast range, and a station label. The OLED surface is dense with data and cold by design. A bedside device earns emotional warmth from one small living thing on screen — a creature that quietly reflects the environment rather than demanding attention. The challenge is to add that warmth without breaking what already works: the device's promise of "inform without intruding," the strict night-mode contrast budget, the 10-minute wake / deep-sleep rhythm, and the existing 256×64 grayscale canvas that already carries the clock + weather UI. + +### Key Decisions + +**Pet form: abstract blob, not a recognizable creature.** +At 24×24 grayscale, recognizable species lose legibility under property morphing. A blob / egg / cloud-like form lets shape, eye state, and surface texture all vary continuously without breaking character. **Why:** signal legibility at the canvas budget. + +**Visual expression: four independent property layers on a shared base, not one fused "mood."** +The four signals (time, weather, indoor T/RH, music) each drive one layer — shape, eye state, surface texture, motion mode. Layers coexist and overlay rather than collapsing into a single named mood. **Why:** "combine all four" was the user's stated product intent; layers keep each signal legible at small canvas size where a fused mood would read muddy. + +**Interaction: fully passive, no button, no sound.** +The pet never chirps, purrs, or reacts to button presses. **Why:** the device is a bedside clock whose core promise is "inform without intruding"; a sound-emitting pet would break the night-mode silence guarantee, and an interactive pet would push the device toward gamification that is out of scope. + +**Persistence: stateless across wake cycles, coherent within one wake.** +Within a 10-minute active period, reactions transition smoothly (e.g., music stops → gentle sway decays over ~15s). At deep-sleep entry, all pet state resets to neutral; the next wake starts clean. **Why:** the user is asleep between wakes, so cross-wake memory has no audience; cross-wake persistence would also require NVS/RTC backup, adding cost for no observable benefit. + +**Placement: bottom-right corner, adjacent to the weather panel.** +Pet lives in the existing 24×24 area now used by the right side of the centered station label. The label is right-aligned (or shortened) to make room. **Why:** natural co-location with weather for the "weather messenger" axis, and out of the time-digits zone. + +```mermaid +flowchart TB + Base["Base idle animation
breathing + float + blink"] + Shape["Shape layer
driven by time of day"] + Eyes["Eye-state layer
driven by time + weather"] + Surface["Surface layer
driven by weather + indoor T/RH"] + Motion["Motion mode layer
driven by music on/off"] + + Base --> Composite["Composite frame
rendered to 24x24"] + Shape --> Composite + Eyes --> Composite + Surface --> Composite + Motion --> Composite +``` + +### Requirements + +#### Pet placement & layout + +- R1. The pet occupies a 24×24 pixel region anchored to the bottom-right of the 256×64 clock canvas, with its top-left at approximately `(232, 40)`. The pet region never overlaps or obscures the time digits, the weather text, or the temperature range. +- R2. The pet renders onto the same LVGL canvas buffer (`canvas_buf` in `main/clock_screen.c`) that already carries clock, weather, and station label, and follows the same draw order rules (after weather panel content, alongside or after station label). +- R3. The existing `station_label` is shortened to a maximum of 16 characters (truncated with an ellipsis if longer) and remains centered within the bottom row, leaving the rightmost ~32 pixels of that row free for the pet region. + +```mermaid +flowchart LR + subgraph Canvas["256x64 canvas"] + direction TB + A["Time label
0,2 size 128x48"] --- B["Weather label
135,0"] + B --- C["Temp range
135,18"] + C --- D["Station label
right-aligned, y=36"] + D --- E["PET
232,40 24x24"] + end +``` + +#### Idle animation (always running) + +- R4. The pet has a continuous idle animation — breathing, gentle float, periodic blink — that runs whenever the pet is visible, independent of all property layers. +- R5. The idle animation does not pause or stop when any property layer becomes active. + +#### Property layers (one per environmental signal) + +- R6. **Shape layer** is driven by time of day: tall posture in active hours, rounder posture approaching wind-down, flat posture near sleep. +- R7. **Eye-state layer** is driven by time of day and outdoor weather: wide open mid-day, half-lidded in evening, nearly closed near sleep; squinted in bright sun, relaxed in rain, soft in fog. +- R8. **Surface layer** is driven by outdoor weather and indoor T/RH: smooth under comfortable conditions; one or more small sweat dots when indoor temperature rises above 26 °C (sweat expression) or falls below 18 °C (dryness texture); a droplet in rain; tiny dot accents in snow. Humidity contributes only via the hot branch (sweat dots intensify above 60 % RH) — the cold/dry branch is temperature-driven only. +- R9. **Motion layer** is driven by music playback state — the simple on/off signal from `audio_player_wrapper`, no BPM or beat detection: idle breathe when no music, gentle bob when music plays, closed-eye soft sway when music has been playing for several seconds. + +#### Coherence & transition within a wake cycle + +- R10. Property changes within a wake cycle are visually coherent — either a smooth transition (over ~1–3 seconds) or a brief reaction animation — rather than instant frame swaps. +- R11. Reaction animations decay after the triggering event ends. When music stops, the bob decays to idle breathe within ~10–30 seconds. When a weather change reverses, the surface element fades within ~10–30 seconds. + +#### Night mode & wake reset + +- R12. The pet is hidden whenever `clock_screen.c::night_mode` is true (the same condition that already hides weather UI and switches to dim clock digits). +- R13. Each wake cycle begins the pet from a neutral baseline — no memory of the prior wake's shape, eye state, surface, or motion state. No NVS or RTC backup of pet state is required. + +### Key Flows + +- F1. Wake cycle boot + - **Trigger:** `clock_screen_create()` / first frame after wake. + - **Steps:** Pet appears at neutral baseline (mid-shape, eyes open, smooth surface, idle breathing). Property layers begin reading current environment signals and apply their initial state. + - **Covers:** R4, R6, R7, R8, R9. + +- F2. Music playback transition + - **Trigger:** Audio player starts (`audio_player_wrapper` state change). + - **Steps:** Motion layer switches from idle breathe to gentle bob; if music persists past ~5s, eye-state layer transitions to closed-eye soft sway. + - **Covers:** R9, R10. + +- F3. Music stops + - **Trigger:** Audio player reports stopped. + - **Steps:** Bob continues for a short decay period (~15s), then motion layer eases back to idle breathe; eyes reopen. + - **Covers:** R9, R11. + +- F4. Outdoor weather update + - **Trigger:** AMAP weather fetch delivers new conditions (e.g., rain, snow, fog, sun, clouds). + - **Steps:** Surface layer transitions to the matching texture (droplet / dots / wisp / clear); eye-state layer adjusts (squint for sun, soft for fog, neutral for clouds); smooth blend over ~1–3s. + - **Covers:** R8, R7, R10. + +- F5. Indoor T/RH threshold crossing + - **Trigger:** SHTC3 read crosses a configured comfortable range (hot / cold / dry / humid). + - **Steps:** Surface layer adds or removes a sweat dot (hot) or dryness texture (cold/dry). Coexists with weather-driven surface elements. + - **Covers:** R8. + +- F6. Time-of-day progression across the wake window + - **Trigger:** RTC minute tick during the 10-minute active period. + - **Steps:** Shape layer and eye-state layer gradually transition based on `tm_hour` (e.g., 22:00 in the wake window starts the "approaching sleep" posture). + - **Covers:** R6, R7, R10. + +- F7. Night mode entry / exit + - **Trigger:** `clock_screen_is_night_time()` returns true / false. + - **Steps:** Pet hides alongside weather UI on entry; returns to neutral baseline on exit. No state preservation across the hidden interval. + - **Covers:** R12, R13. + +- F8. Deep sleep entry + - **Trigger:** 10-minute wake period ends; ESP32-C3 enters deep sleep. + - **Steps:** All pet state is discarded. The next wake (in 10 minutes via `esp_deep_sleep_enable_gpio_wakeup`) starts from F1 with neutral baseline. + - **Covers:** R13. + +### Acceptance Examples + +- AE1. Wake at 07:00 on a sunny morning with music playing — pet appears tall (R6 awake), eyes squinted slightly (R7 bright sun), surface smooth (R8 dry), bobbing gently (R9 music). All four property layers are independently visible at once. **Covers:** R4, R6, R7, R8, R9. + +- AE2. Wake at 23:00 on a rainy night with no music — pet appears flat (R6 sleepy), eyes nearly closed (R7 night + rain), one droplet on the surface (R8 rain), idle breathing (R9 no music). **Covers:** R4, R6, R7, R8, R9. + +- AE3. Music stops mid-track during a wake — pet continues gentle sway for ~15 seconds, then motion layer eases back to idle breathe and eyes reopen (R9, R11). The transition is smooth, not abrupt. + +- AE4. Time crosses 22:00 during a wake — pet hides alongside weather UI; clock digits dim to night-mode contrast (R12). At 06:00 next morning, pet returns to neutral baseline with no memory of the prior day's shape or eye state (R13). + +- AE5. Indoor temperature rises past the configured hot threshold while weather is sunny — surface layer adds a single sweat dot in addition to its smooth default; shape and motion layers are unaffected (R8 coexistence with weather and time signals). + +- AE6. User presses the wake button during a music playback — music stops via the existing audio flow; the pet does not react directly to the button press and continues bobbing until the F3 decay timer completes (Key Decision: interaction is fully passive). + +- AE7. Pet is hidden during night mode. User manually forces day-mode (`s_night_override = 0`) at 02:00 — pet reappears at neutral baseline, time-of-day layer reflects 02:00 (post-midnight posture), and stays legible under night-mode contrast budget. **Covers:** R12 + R4. + +### Scope Boundaries + +**Deferred for later** + +- Pet sound output (chirp when music starts, gentle ambient cooing). +- Pet button interaction (tap-to-pet, double-tap to switch mode). +- Cross-wake pet growth, evolution, or "mood memory" (intentionally rejected in dialogue). +- Dedicated full-screen "pet view" toggled by a button. +- Music reactivity driven by BPM / beat detection (current scope uses music on/off only). +- Pet species selection / multiple pet skins. +- Pet-specific configuration screen (e.g., threshold tuning for "hot" / "rainy"). + +**Outside this product's identity** + +- Tamagotchi-style care mechanics (feeding, hunger, affection scores) — the device is a bedside clock, not a virtual pet; care mechanics would push it toward gamification that conflicts with the "inform without intruding" promise. + +### Outstanding Questions + +*All blocking questions resolved at brainstorm time — see R3, R8, R9 for the resolved values.* + +**Deferred to Planning** + +- Sprite / frame asset strategy: inline C byte arrays vs LVGL image objects vs programmatic pixel draw. +- Animation tick source: custom FreeRTOS timer vs LVGL timer vs reuse of `clock_screen.c`'s existing 1-second tick. `lv_conf.h` disables LVGL animations, so a manual frame-swap approach is required. +- Property layer state machine: continuous lerp vs discrete tick-based state tables. +- Drawing-order placement: pet drawn after weather panel but before station label, or after both. +- Render hook: extend `clock_screen.c` directly, or add a small `pet_renderer.c` pulled into the same draw loop. + +### Sources / Research + +- `main/clock_screen.c:16-19` — `CANVAS_W=256`, `CANVAS_H=64`, `TIME_W=128`, `SEP_X=128`. Confirms the right half is shared by weather, temp range, and (currently) a centered station label. +- `main/clock_screen.c:282-321` — weather, temp, and station label creation / positioning. The pet's bottom-right corner must not collide with the station label's current centered placement. +- `main/clock_screen.c:37-38, 71-72, 174, 230` — `night_mode` flag drives the dim 7-segment clock and hides the chart. Pet hides under the same flag. +- `main/ssd1322_driver.h:16-17` — `LCD_H_RES=256`, `LCD_V_RES=64`. Canvas dimensions. +- `main/lv_conf.h:65` — LVGL animations are disabled. Pet animation must be implemented via manual frame-swap on a timer. +- `components/esp-audio-player/` — existing MP3/WAV playback infrastructure; provides the music on/off signal R9 needs. +- `components/shtc3/shtc3.c` and `components/pcf85063/pcf85063.c` — indoor T/RH and RTC sources, on the shared I²C bus. Both signals are already in the active loop. +- `main/Kconfig.projbuild` — `CONFIG_NIGHT_START_HOUR` / `CONFIG_NIGHT_END_HOUR` define the night-mode window; no new config is needed for the pet's hide/show behavior unless threshold tuning is added later. \ No newline at end of file diff --git a/docs/plans/pet-simulator.html b/docs/plans/pet-simulator.html new file mode 100644 index 0000000..66b96a7 --- /dev/null +++ b/docs/plans/pet-simulator.html @@ -0,0 +1,663 @@ + + + + +SlumberCube · Pixel Pet Simulator (NES / FC Cats) + + + +

SLUMERCUBE / PIXEL PET · NES (FC) STYLE

+
4-tone palette · hard edges · iconic NES silhouettes.
+ + + +
+
+

Eyes

+
+ ● ● awake + - - half + ~ ~ happy + × × squint +
+
+
+

Surface

+
+ none + ♪ ♪ music + · · rain + * * snow +
+
+
+

Motion

+
+ idle · breathe + bob (music) + soft sway +
+
+
+

Environment

+ + + +
+
+ +
+ 07:00 · sunny · music + 23:00 · rainy night + 14:00 · hot indoor + 19:00 · snow · music +
+ +
click any pixel across the 4 cats to inspect its level
+
NES palette: #L0 outline · +L8 body · oL11 light · WL15 eye white · @L0 pupil
+ + + +