---
phase: 05-reliability-documentation
plan: 02
type: execute
wave: 2
depends_on: ["05-01"]
files_modified:
  - docs/user-guide/animation.md
  - docs/user-guide/ceiling-lights.md
autonomous: true
requirements: [DOCS-02]
must_haves:
  truths:
    - "A streaming consumer reading docs/user-guide/animation.md finds a section stating what the animation layer now handles: frame delivery paced against device acknowledgements with latest-frame-wins (dropped frames are skipped, never queued), with no consumer-facing configuration"
    - "The section contains an explicit do-not-reimplement list: consumers must not add their own acknowledgement tracking, keepalive daemons, or frame-retry wrappers"
    - "The section contains a minimal streaming loop example — generate frames, call send_frame() at the chosen FPS — in the page's existing uint16-tuple style"
    - "Per-device-class FPS guidance appears with concrete numbers: ~20 FPS platform ceiling over WiFi/Set64; larger multi-packet matrix devices saturate sooner — the Ceiling Capsule (16×8 zones, 3 packets per frame) sustains ~10 FPS"
    - "The oversending symptom is documented: stutter caused by latest-frame-wins dropping frames — degradation by design, never a backlog or freeze"
    - "animation.md carries no inflated FPS claims and its example sleeps pace at 20 FPS"
    - "animation.md links to the troubleshooting wake-tail anchor, and ceiling-lights.md links to the same anchor (D5-02)"
  artifacts:
    - path: "docs/user-guide/animation.md"
      provides: "DOCS-02 streaming-consumer guidance + stale-claim fixes"
      contains: "latest-frame-wins"
    - path: "docs/user-guide/ceiling-lights.md"
      provides: "D5-02 cross-link to the wake-tail section"
      contains: "troubleshooting.md#gen4-power-save-wake-tail"
  key_links:
    - from: "docs/user-guide/animation.md"
      to: "docs/user-guide/troubleshooting.md"
      via: "relative link to the wake-tail anchor where streaming meets gen4 power-save"
      pattern: "troubleshooting\\.md#gen4-power-save-wake-tail"
    - from: "docs/user-guide/ceiling-lights.md"
      to: "docs/user-guide/troubleshooting.md"
      via: "relative link to the wake-tail anchor"
      pattern: "troubleshooting\\.md#gen4-power-save-wake-tail"
  prohibitions:
    - statement: "MUST NOT restate internal flow-control tuning constants (ack-probe placement, outstanding-ack gate threshold, ack expiry) in user-guide prose — behavioural contract only; they are internal and may change (D5-09)"
      category: values
      requirement_id: DOCS-02
    - statement: "MUST NOT describe latest-frame-wins frame drops as a defect, and MUST NOT advise retrying a dropped frame — the correct recovery is simply the next frame"
      category: values
      requirement_id: DOCS-02
---

<objective>
Write the DOCS-02 streaming-consumer guidance (LedFx pattern) into
docs/user-guide/animation.md per D5-07/D5-08/D5-09, fix that page's own stale
pre-v1.1 claims in the same pass (D5-10 audit findings #1–#4), and add the two
remaining D5-02 cross-links (animation.md ↔ wake-tail; ceiling-lights.md → wake-tail).

Depends on plan 05-01, which created the `#gen4-power-save-wake-tail` anchor in
troubleshooting.md that both cross-links in this plan target.

Purpose: a streaming consumer can see exactly what the library now owns (ack-gated
pacing, latest-frame-wins — D4-01/D4-02 behavioural wording) and what they must not
reimplement, with honest per-device-class FPS numbers sourced from the 04-13 operator
verdict.

Output: updated docs/user-guide/animation.md and docs/user-guide/ceiling-lights.md;
docs build stays clean (exit 0, no warning naming an edited page — the anchor
cross-links must resolve).

Decisions implemented: D5-01, D5-02 (inbound half), D5-07, D5-08, D5-09, D5-10.
</objective>

<execution_context>
@$HOME/.claude/gsd-core/workflows/execute-plan.md
@$HOME/.claude/gsd-core/templates/summary.md
</execution_context>

<context>
@.planning/phases/05-reliability-documentation/05-CONTEXT.md
@.planning/phases/05-reliability-documentation/05-RESEARCH.md
@.planning/phases/05-reliability-documentation/05-PATTERNS.md
@.planning/phases/05-reliability-documentation/05-01-SUMMARY.md
</context>

## Flagged planner assumption (spec-less edge probe — surfaced, not dropped)

The edge probe could not classify DOCS-02 (probe output: "unclassified — review
manually"). Planner assumption: DOCS-02's edge risk is fully carried by the D5-08 and
D5-09 truths above (concrete per-device FPS numbers; behavioural-contract-only scope —
no internal constants). This assumption is flagged for human review at verify time; if
the verifier finds an uncovered DOCS-02 edge, route it back as a gap rather than
passing silently.

<tasks>

<task type="auto">
  <name>Task 1: Fix animation.md's stale pre-v1.1 claims (audit #1–#4)</name>
  <files>docs/user-guide/animation.md</files>
  <read_first>
    - docs/user-guide/animation.md (current state; intro at lines 1–15; example sleeps at lines 44, 71, 276; "Flickering or Glitches" at lines 331–340; canonical loop shape at lines 34–48)
    - .planning/phases/05-reliability-documentation/05-RESEARCH.md (§Stale-Content Audit Findings items #1–#4; §Verified Numbers — FPS facts)
  </read_first>
  <action>
    Four in-place fixes, per audit findings #1–#4 (D5-10):

    1. Line 4: the intro claims a thirty-plus-FPS design target. Reword to the
       ~20 FPS platform ceiling framing — the animation system is designed for
       real-time effects at up to ~20 FPS over WiFi (a platform ceiling of the
       WiFi/Set64 wire path, not a client limitation).
    2. Line 10: the "High frame rates" bullet cites a twenty-plus figure; adjust to
       "up to ~20 FPS" so it matches the ceiling framing.
    3. Lines 44, 71 and 276: three example sleeps currently pace at thirty FPS.
       Change each sleep divisor to 20 so each reads `asyncio.sleep(1 / 20)`; update
       the trailing comment on line 44 to say 20 FPS.
    4. "Flickering or Glitches" (lines 331–340): the Cause is given as network packet
       loss and the advice ends by accepting UDP loss as normal — superseded.
       Rewrite the entry to distinguish the two causes:
       - Primary (by design): device saturation — the frame rate exceeds what the
         device sustains, so latest-frame-wins drops frames and the animation
         stutters. Fix: reduce FPS toward the device's sustainable rate (see the
         streaming section this plan adds; `stats.gated` from `send_frame()` shows
         when frames are being dropped). Never a backlog or freeze.
       - Secondary (genuine): network loss — weak WiFi signal to the device; fix
         signal or use a stronger AP.
       Remove the resign-yourself-to-UDP-loss advice entirely.

    Do not touch the "Monitor Statistics" section or any other section in this task.
    Australian English.
  </action>
  <verify>
    <automated>! grep -q '1 / 30' docs/user-guide/animation.md && ! grep -qE '30\+ FPS' docs/user-guide/animation.md && ! grep -q 'packet loss is normal' docs/user-guide/animation.md && ! grep -q 'instead of 30' docs/user-guide/animation.md && uv run zensical build</automated>
  </verify>
  <acceptance_criteria>
    - `grep -c '1 / 30' docs/user-guide/animation.md` outputs 0 and `grep -c '1 / 20' docs/user-guide/animation.md` outputs 3 (all three sleeps now pace at 20 FPS)
    - `grep -cE '30\+ FPS' docs/user-guide/animation.md` outputs 0; `grep -q '~20 FPS' docs/user-guide/animation.md` succeeds (intro reframed)
    - `grep -c 'packet loss is normal' docs/user-guide/animation.md` outputs 0 and `grep -c 'instead of 30' docs/user-guide/animation.md` outputs 0
    - The Flickering entry names device saturation as the primary cause and mentions `stats.gated`
    - `uv run zensical build` exits 0; `uv run zensical build 2>&1 | grep -i warning | grep -c 'animation'` outputs 0
  </acceptance_criteria>
  <done>No inflated FPS claims remain; the Flickering entry distinguishes saturation stutter (by design) from genuine network loss; build clean.</done>
</task>

<task type="auto">
  <name>Task 2: Add the streaming-consumer section + D5-02 cross-links</name>
  <files>docs/user-guide/animation.md, docs/user-guide/ceiling-lights.md</files>
  <read_first>
    - docs/user-guide/animation.md (post-Task-1 state; `## Performance Tips` at line 245 ends before `## Troubleshooting` at line 329 — the insertion point; setup pattern at lines 54–62; canonical loop at lines 34–48)
    - .planning/phases/05-reliability-documentation/05-RESEARCH.md (§Shipped Animation Behaviour — the D4-01/D4-02 behavioural wording to use and the D5-09 boundary of what NOT to restate; §Verified Numbers — FPS table; §Code Examples — minimal streaming loop)
    - .planning/phases/05-reliability-documentation/05-01-SUMMARY.md (confirms the exact wake-tail anchor as written by plan 05-01)
    - docs/user-guide/ceiling-lights.md (link/admonition style; pick a natural spot for the wake-tail cross-link)
  </read_first>
  <action>
    1. Insert a new section into docs/user-guide/animation.md between the end of
       `## Performance Tips` content and the `## Troubleshooting` heading (line 329
       pre-edit). Heading MUST be exactly `## Streaming and Flow Control` (pinned for
       determinism; no other page links to it this phase). Content per D5-07/D5-08,
       written from the D4-01/D4-02 behavioural summary — NOT from the Animator
       docstring (Pitfall 3; the docstring names internal tuning constants that must
       not be restated, per D5-09):

       - Short narrative (D5-07): since v1.1 the animation layer paces frame delivery
         against device acknowledgements internally. When a device falls behind, new
         frames are dropped, never queued — latest-frame-wins. There is no
         consumer-facing configuration: consumers call the same Animator API and the
         library decides delivery strategy. Delivery is paced against device acks —
         state observable behaviour only.
       - Explicit do-not-reimplement list (D5-07, written for the LedFx integration
         pattern): do not add your own acknowledgement tracking; do not add keepalive
         daemons; do not add frame-retry wrappers. Retrying a dropped frame is
         actively wrong — latest-frame-wins means the correct recovery is simply the
         next frame.
       - Minimal streaming loop (D5-07), matching the existing page style (raw uint16
         HSBK tuples, synchronous `send_frame()`, try/finally + `animator.close()`):
         the RESEARCH §Code Examples loop — `target_fps = 20` with the comment that
         it is the platform ceiling over WiFi and large matrix devices sustain less;
         body generates a frame ("your only job: produce frames"), calls
         `animator.send_frame(frame)` ("the library paces delivery"), then
         `await asyncio.sleep(1 / target_fps)`.
       - Per-device-class FPS guidance (D5-08), concrete numbers from §Verified
         Numbers: ~20 FPS is the platform ceiling over WiFi/Set64; larger
         multi-packet matrix devices saturate sooner — the Ceiling Capsule (16×8
         zones — 128 zones — at 3 packets per frame) sustains ~10 FPS. Explain the
         observable symptom of oversending: stutter caused by latest-frame-wins
         dropping frames — degradation by design, never a backlog or freeze. A brief
         aside MAY note `stats.gated` / `stats.acks_outstanding` (public
         AnimatorStats fields) as the way to observe gating; keep the minimal loop
         minimal.
       - D5-02 cross-link: one sentence linking gen4 power-save to streaming —
         relative link `troubleshooting.md#gen4-power-save-wake-tail` (verify the
         anchor against 05-01-SUMMARY.md first).

    2. Add ONE cross-link to docs/user-guide/ceiling-lights.md (D5-02): a single
       sentence at a natural spot (placement discretionary — e.g. near power/first-
       command content or the page's See Also/links area) noting Ceiling devices are
       gen4 and linking to `troubleshooting.md#gen4-power-save-wake-tail` for the
       wake-tail guidance. No other changes to that page; no new admonitions needed.

    Do NOT write the outstanding-ack gate threshold, ack expiry duration, or which
    packet carries the ack probe anywhere in either page (D5-09). Australian English;
    "CCT/brightness-only" terminology if device classes are described.
  </action>
  <verify>
    <automated>grep -q '^## Streaming and Flow Control' docs/user-guide/animation.md && grep -q 'latest-frame-wins' docs/user-guide/animation.md && grep -q '10 FPS' docs/user-guide/animation.md && grep -q 'troubleshooting.md#gen4-power-save-wake-tail' docs/user-guide/animation.md && grep -q 'troubleshooting.md#gen4-power-save-wake-tail' docs/user-guide/ceiling-lights.md && uv run zensical build</automated>
  </verify>
  <acceptance_criteria>
    - `grep -c '^## Streaming and Flow Control' docs/user-guide/animation.md` outputs 1
    - Section contains `latest-frame-wins`, a do-not-reimplement list naming acknowledgement tracking, keepalives, and frame-retry wrappers, and `send_frame(` in a fenced python block with `target_fps = 20`
    - FPS numbers present: `grep -q '~20 FPS' docs/user-guide/animation.md` and `grep -q '~10 FPS' docs/user-guide/animation.md` succeed; the Capsule shape appears as 16×8 zones with 3 packets per frame
    - Oversending symptom documented: section contains "stutter" and states drops are by design (never a backlog or freeze)
    - D5-09 boundary held: `grep -c 'expiry' docs/user-guide/animation.md` outputs 0 and `grep -cE 'gate (of|at) 2|2 outstanding' docs/user-guide/animation.md` outputs 0 (no tuning constants restated)
    - Both cross-links present: `grep -c 'troubleshooting.md#gen4-power-save-wake-tail' docs/user-guide/animation.md` outputs >= 1 and same grep on docs/user-guide/ceiling-lights.md outputs 1
    - `uv run zensical build` exits 0; `uv run zensical build 2>&1 | grep -i warning | grep -cE '(animation|ceiling-lights|troubleshooting)'` outputs 0 (anchors resolve)
  </acceptance_criteria>
  <done>The streaming section exists with the D4-01/D4-02 behavioural contract, do-not-reimplement list, minimal loop, and per-device-class FPS numbers; both D5-02 cross-links resolve; no internal tuning constants appear; build clean.</done>
</task>

</tasks>

<threat_model>
## Trust Boundaries

| Boundary | Description |
|----------|-------------|
| published docs → reader behaviour | Streaming guidance shapes how downstream consumers (LedFx) drive real devices |
| internal implementation → public contract | Internal tuning constants must not become documented API (Hyrum's-law exposure) |

## STRIDE Threat Register

| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
| T-05-04 | Information Disclosure | docs/user-guide/animation.md | low | mitigate | D5-09 prohibition + negative greps keep internal tuning constants (gate threshold, ack expiry, probe placement) out of user-guide prose |
| T-05-05 | Denial of Service | streaming example (reader-side device flooding) | low | mitigate | Example pinned at 20 FPS with explicit saturation guidance (~10 FPS for Capsule-class) — consistent with the ~20 msg/sec device limit |
| T-05-SC | Tampering | npm/pip/cargo installs | low | accept | No package-manager installs occur in this phase (RESEARCH §Package Legitimacy Audit: none) |
</threat_model>

<verification>
- `uv run zensical build` exits 0 after every task; no warning names animation.md, ceiling-lights.md, or troubleshooting.md (both new anchor links resolve); warning total stays at the pre-edit baseline (RESEARCH.md records 8, all in docs/api/ pages this phase never edits)
- All FPS figures trace to 05-RESEARCH.md §Verified Numbers / the 04-13 operator verdict in STATE.md — no invented numbers (D5-08)
- mkdocs.yml unchanged (D5-01); docs/api/*.md, docs/changelog.md, and source files unchanged
</verification>

<success_criteria>
- DOCS-02 satisfied: a streaming consumer can find what the animation layer handles (ack-gated pacing, latest-frame-wins) and what not to reimplement (own acks, keepalives, frame-retry wrappers), with concrete per-device-class FPS numbers
- Audit findings #1–#4 fixed; the page carries the ~20 FPS ceiling framing throughout
- All three D5-02 cross-link directions now exist (troubleshooting→animation from plan 05-01; animation→wake-tail and ceiling-lights→wake-tail from this plan)
</success_criteria>

## Artifacts this phase produces (this plan's share)

- New heading/anchor: `docs/user-guide/animation.md` → `## Streaming and Flow Control` (`#streaming-and-flow-control`)
- Rewritten troubleshooting entry: `### Flickering or Glitches` (heading retained, body replaced) in `docs/user-guide/animation.md`
- New cross-link sentence in `docs/user-guide/ceiling-lights.md`
- Modified files: `docs/user-guide/animation.md`, `docs/user-guide/ceiling-lights.md`
- No code symbols, no new pages, no nav entries

<output>
Create `.planning/phases/05-reliability-documentation/05-02-SUMMARY.md` when done.
Record in the summary: the flagged DOCS-02 edge-probe assumption above (carried to verification), and confirmation the D5-09 boundary held (no tuning constants in prose).
</output>
