---
phase: 04-animation-flow-control
plan: 10
type: execute
wave: 8
depends_on:
  - "04-09"
files_modified:
  - .planning/ROADMAP.md
  - .planning/REQUIREMENTS.md
  - .planning/STATE.md
  - .planning/phases/04-animation-flow-control/04-06-PLAN.md
  - .planning/phases/04-animation-flow-control/04-07-PLAN.md
  - .planning/phases/04-animation-flow-control/uat_ack_stream.py
  - .planning/phases/04-animation-flow-control/04-UAT-TILES.json
autonomous: true
gap_closure: true
requirements:
  - ANIM-03

must_haves:
  truths:
    - "ROADMAP Phase 4 success criterion 3 carries the operator-approved replacement wording (pooled loss <= 5.0% per run and <= 9.0% in any single round, with the evidence-derived citation) — content verbatim from 04-GAP-ANALYSIS.md '## Proposed amendment'"
    - "REQUIREMENTS ANIM-03's loss clause reads the approved replacement (pooled <= 5.0% per run, <= 9.0% per round; blind-fire baseline 14.6%) and its checkbox stays UNCHECKED until 04-06 Task 2 approval"
    - "uat_ack_stream.py enforces the amended gate — MAX_QUERY_LOSS_PCT = 9.0 per round plus MAX_POOLED_QUERY_LOSS_PCT = 5.0 across all rounds of a run — and its module docstring exit-code contract and _thresholds() JSON agree with the code, so the written contract never disagrees with what runs"
    - "A fresh 04-UAT-TILES.json exists, measured by the amended harness (its thresholds block records 9.0/5.0), recorded exactly as the exit code says — PASS, FAIL, or ENV-ERROR — with the amendment commits strictly preceding the evidence commit in git history"
    - "No contradictory gates are left standing: 04-06-PLAN.md and 04-07-PLAN.md carry amendment notices superseding their 0%-gate and byte-identical-to-04-05 criteria, and the STATE.md UAT sequencing gate note reflects the post-run state"
    - statement: "04-06 Task 2 remains the sole owner of the ANIM-03 visual checkpoint (the operator is asked exactly once), executes only after this plan's headless PASS, and ANIM-03 completes only when both the amended-gate headless PASS and the operator visual verdict exist"
      verification: backstop
  artifacts:
    - path: ".planning/phases/04-animation-flow-control/04-UAT-TILES.json"
      provides: "fresh ANIM-03 headless evidence measured under the amended gate (or an honestly recorded FAIL/ENV-ERROR outcome)"
    - path: ".planning/phases/04-animation-flow-control/uat_ack_stream.py"
      provides: "the amended harness: 9.0% per-round + 5.0% pooled gates with a self-consistent docstring contract and thresholds JSON"
    - path: ".planning/ROADMAP.md"
      provides: "Phase 4 success criterion 3 in the operator-approved amended form"
    - path: ".planning/REQUIREMENTS.md"
      provides: "ANIM-03 loss clause in the operator-approved amended form"
  key_links:
    - from: ".planning/phases/04-animation-flow-control/uat_ack_stream.py"
      to: ".planning/phases/04-animation-flow-control/04-GAP-ANALYSIS.md"
      via: "gate constants/wording applied verbatim from the '## Proposed amendment' section the operator endorsed (04-09-SUMMARY.md, verbatim reply '1')"
      pattern: "MAX_QUERY_LOSS_PCT.*9.0|MAX_POOLED_QUERY_LOSS_PCT.*5.0"
    - from: ".planning/phases/04-animation-flow-control/04-UAT-TILES.json"
      to: ".planning/phases/04-animation-flow-control/uat_ack_stream.py"
      via: "written by the amended fixed-contract harness"
      pattern: "uat_ack_stream.py --profile tiles --rounds 3"
---

<objective>
Apply the operator-approved ANIM-03 recalibration (04-09 routing decision: verdict H2,
operator replied verbatim "1" = recalibrate) and re-establish the ANIM-03 headless
evidence under the amended gate. Three moves, in commit order: (1) amend ROADMAP
success criterion 3 and REQUIREMENTS ANIM-03 with the wording the operator endorsed
in 04-GAP-ANALYSIS.md "## Proposed amendment", annotating the now-superseded 04-06/04-07
gate criteria; (2) amend the harness gate — MAX_QUERY_LOSS_PCT 0.0 → 9.0 per round plus
a new MAX_POOLED_QUERY_LOSS_PCT = 5.0 pooled check — keeping docstring, contract and
thresholds JSON self-consistent; (3) run the amended harness fresh (3 rounds, Tiles)
and record whatever it honestly measures.

Purpose: the 04-09 decision landed but NOTHING has been applied — the previous
04-UAT-TILES.json was measured under the miscalibrated 0% gate (exit 1). This plan is
the follow-up the recalibrate route requires before 04-06 Task 2 (visual checkpoint)
and 04-07 (Ceiling UAT) can unblock. Scope honours D4-05: src/ and tests/ are untouched
(nothing to fix — R3 did not fire; the shipped path outperforms the spike methodology,
Fisher p = 0.0342), and D4-01's src/ tuning constants (gate 2 outstanding, ~1 s expiry)
are NOT what changes here — only the UAT pass thresholds are recalibrated.
Output: amended governing documents + amended harness + fresh 04-UAT-TILES.json.
</objective>

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

<context>
@.planning/STATE.md
@.planning/phases/04-animation-flow-control/04-GAP-ANALYSIS.md
@.planning/phases/04-animation-flow-control/04-09-SUMMARY.md
@.planning/phases/04-animation-flow-control/04-08-SUMMARY.md
@.planning/phases/04-animation-flow-control/04-CONTEXT.md
@.planning/phases/04-animation-flow-control/uat_ack_stream.py
</context>

<tasks>

<task type="auto">
  <name>Task 1: Apply the operator-approved amendment to ROADMAP and REQUIREMENTS; annotate the superseded 04-06/04-07 gate criteria</name>
  <files>.planning/ROADMAP.md, .planning/REQUIREMENTS.md, .planning/phases/04-animation-flow-control/04-06-PLAN.md, .planning/phases/04-animation-flow-control/04-07-PLAN.md</files>
  <read_first>
    - .planning/phases/04-animation-flow-control/04-GAP-ANALYSIS.md — the "## Proposed amendment" section is the operator-approved wording, verbatim; the "## Verdict" section is the evidence basis
    - .planning/phases/04-animation-flow-control/04-09-SUMMARY.md — the operator's verbatim routing decision ("1" = recalibrate, endorsing the brief's amendment)
    - .planning/ROADMAP.md — "### Phase 4: Animation Flow Control" Success Criteria list and the Phase 4 plan list
    - .planning/phases/04-animation-flow-control/04-06-PLAN.md and 04-07-PLAN.md — the plans whose gate criteria this task supersedes by annotation (their bodies are NOT rewritten)
  </read_first>
  <action>
    Apply the operator-approved wording exactly as written in 04-GAP-ANALYSIS.md
    "## Proposed amendment". The operator approved that text verbatim (04-09-SUMMARY.md,
    reply "1") — any deviation beyond the whitespace reflow noted below must be flagged
    in the summary, never silently reworded.

    1. **ROADMAP.md — Phase 4 success criterion 3.** In "### Phase 4: Animation Flow
       Control" → "**Success Criteria**", replace list item 3 (the item beginning
       "3. Hardware UAT (standard Tiles):") with the brief's proposed replacement.
       Content verbatim; the ONLY permitted deviation is reflowing the brief's wrapped
       lines onto a single line to match the ROADMAP list style. The replacement content:

       > 3. Hardware UAT (standard Tiles): under 20 FPS streaming, concurrent queries to the same device stay within the measured device floor — pooled loss ≤ 5.0% per run and ≤ 9.0% in any single round (evidence-derived: 15/540 = 2.78% pooled across all shipped-path measurements, Wilson 95% upper bound 4.53%, per 04-UAT-TILES-run1-FAIL.json, 04-UAT-TILES.json and 04-GAP-INVESTIGATION.json; blind-fire baseline: 14.6%) — over repeated rounds

       Wording quirk to record in the summary, not to alter: the citation names
       04-UAT-TILES.json, which Task 3 overwrites with amended-gate evidence — the cited
       miscalibrated-gate bytes remain at commit 5de88f6. Apply the wording as approved.

       Also update the Phase 4 plan-list bullet for 04-06-PLAN.md so it no longer
       describes a live 0%-gate measurement. Replace that bullet's text with:

       > - [ ] 04-06-PLAN.md — ANIM-03 Tiles UAT: operator visual-smoothness checkpoint (human gate) — Task 1 (headless measurement under the miscalibrated 0% gate, FAILed twice at ~3.4% pooled) superseded by 04-10's recalibrated re-run; Task 2 executes after 04-10 passes

       Likewise refresh the 04-07-PLAN.md bullet, whose trailing "gated on 04-09
       routing" clause goes stale once this task applies the routing. Replace that
       bullet's text with:

       > - [ ] 04-07-PLAN.md — ANIM-04 Ceiling Capsule UAT: power-on, 13×26 framebuffer-path measurement with CopyFB ack RTTs + operator visual checkpoint (human gate) — gated on 04-06 Task 2 approval; inherits the amended shared gate (9.0%/round + 5.0% pooled) per the 04-10 annotation

    2. **REQUIREMENTS.md — ANIM-03 loss clause.** Replace the ANIM-03 bullet (which
       currently demands a 0% target) with the brief's proposed replacement, preserving
       the "- [ ]" checkbox (UNCHECKED — completion belongs to 04-06 Task 2) and the
       file's two-space continuation indent:

       > - [ ] **ANIM-03**: Hardware validation on standard Tiles: concurrent queries under 20 FPS
       >   streaming show loss within the measured device floor (pooled ≤ 5.0% per run,
       >   ≤ 9.0% per round; blind-fire baseline 14.6%)

       Do NOT touch the Traceability table row (ANIM-03 stays "Pending").

    3. **04-06-PLAN.md — supersession annotation (annotate, never rewrite).** Insert the
       following block immediately after the frontmatter's closing "---" (blank line
       either side), leaving every other byte of the plan untouched:

       > **AMENDMENT NOTICE (2026-07-17, applied by 04-10):** The operator routed the
       > ANIM-03 gap to recalibrate (04-09-SUMMARY.md, verbatim reply "1"), adopting the
       > amendment in 04-GAP-ANALYSIS.md "## Proposed amendment". Consequences here:
       >
       > - **Task 1 is SUPERSEDED — do not re-run it from this plan.** Its 0%-loss gate
       >   and its "harness byte-identical to the plan-04-05 commit" acceptance criterion
       >   no longer apply; 04-10 owns the amended-gate headless re-run (per-round
       >   ≤ 9.0%, pooled ≤ 5.0% per run). The original Task 1 FAIL evidence stays in
       >   history unmodified (commit 5de88f6).
       > - **Task 2 (operator visual verdict) remains live** and is the single owner of
       >   the ANIM-03 visual checkpoint. It executes only after 04-10's headless run
       >   PASSes, using the IP 04-10's run actually used.

    4. **04-07-PLAN.md — inherited-gate annotation (annotate, never rewrite).** Insert
       the following block immediately after the frontmatter's closing "---":

       > **AMENDMENT NOTICE (2026-07-17, applied by 04-10):** The operator-approved
       > ANIM-03 recalibration (04-GAP-ANALYSIS.md "## Proposed amendment", routed in
       > 04-09-SUMMARY.md) amended the shared gate in uat_ack_stream.py: per-round query
       > loss ≤ 9.0% plus pooled ≤ 5.0% per run now apply to BOTH profiles, including
       > this plan's ceiling runs. Task 1's "harness file is byte-identical to its
       > plan-04-05 commit" criterion is superseded: the harness must instead be
       > byte-identical to its 04-10 amendment commit. All other criteria (8-packet
       > frame shape, delivered ≥ 85%, CopyFB ack RTT evidence, ENV-ERROR/FAIL handling)
       > are unchanged. This plan still executes only after 04-06 Task 2 approval.

    Commit all four files together with `git commit -s` (GPG-signed, per repo policy):
    `docs(04-10): apply operator-approved ANIM-03 recalibration to governing documents`.
    This commit MUST land before Task 3's evidence commit (protocol-fixed-before-data,
    04-08 discipline).
  </action>
  <verify>
    <automated>grep -c 'stay within the measured device floor' .planning/ROADMAP.md | grep -qx 1 && grep -c 'measured device floor' .planning/REQUIREMENTS.md | grep -qx 1 && ! grep -q 'target 0%' .planning/ROADMAP.md && ! grep -q 'target 0%' .planning/REQUIREMENTS.md && grep -q 'AMENDMENT NOTICE' .planning/phases/04-animation-flow-control/04-06-PLAN.md && grep -q 'AMENDMENT NOTICE' .planning/phases/04-animation-flow-control/04-07-PLAN.md && grep -q '\- \[ \] \*\*ANIM-03\*\*' .planning/REQUIREMENTS.md</automated>
  </verify>
  <acceptance_criteria>
    - ROADMAP Phase 4 success criterion 3 contains, verbatim (single-line reflow only): "stay within the measured device floor — pooled loss ≤ 5.0% per run and ≤ 9.0% in any single round" with the full evidence-derived parenthetical and "blind-fire baseline: 14.6%"
    - REQUIREMENTS ANIM-03 reads "show loss within the measured device floor (pooled ≤ 5.0% per run, ≤ 9.0% per round; blind-fire baseline 14.6%)" and its checkbox is still "- [ ]" (unchecked)
    - `grep -c 'target 0%' .planning/ROADMAP.md` returns 0 and `grep -c 'target 0%' .planning/REQUIREMENTS.md` returns 0
    - 04-06-PLAN.md and 04-07-PLAN.md each contain exactly one "AMENDMENT NOTICE (2026-07-17, applied by 04-10)" block after their frontmatter; every other line of both plans is byte-identical to before this task
    - The commit is signed off (`git commit -s`) and precedes any Task 3 evidence commit
  </acceptance_criteria>
  <done>The operator-approved amendment wording is live in ROADMAP and REQUIREMENTS, the two stale gate criteria are annotated as superseded with pointers to their replacements, and no document still presents the 0% gate as a live criterion.</done>
</task>

<task type="auto">
  <name>Task 2: Amend the harness gate — 9.0% per round + 5.0% pooled — with a self-consistent contract</name>
  <files>.planning/phases/04-animation-flow-control/uat_ack_stream.py</files>
  <read_first>
    - .planning/phases/04-animation-flow-control/uat_ack_stream.py — module docstring (profile descriptions, honest-reporting rules, exit-code contract), the constants block under "Fixed pass thresholds", `_evaluate`, `_thresholds`, and `main()`'s results dict — these are every location the amendment touches
    - .planning/phases/04-animation-flow-control/04-GAP-ANALYSIS.md — "## Proposed amendment" harness clause: MAX_QUERY_LOSS_PCT 0.0 → 9.0 (per-round check, existing `_evaluate` logic unchanged) plus new MAX_POOLED_QUERY_LOSS_PCT = 5.0 applied across all rounds of a run
  </read_first>
  <action>
    Amend .planning/phases/04-animation-flow-control/uat_ack_stream.py exactly per the
    operator-approved clause, plus the contract-consistency updates it mandates. Per
    D4-05 scope, this file is the ONLY code touched — src/ and tests/ stay untouched
    (D4-01's src/ tuning constants — gate limit 2, ~1 s expiry — are not UAT thresholds
    and do not change).

    1. **Constants block** (currently headed by the comment "Fixed pass thresholds (set
       before any run, never adjusted afterwards)."): set `MAX_QUERY_LOSS_PCT` to `9.0`
       (it is currently `0.0`), and add `MAX_POOLED_QUERY_LOSS_PCT: float = 5.0` on the
       next line. Extend the comment block with provenance so the file's own honesty
       trail stays intact, e.g.: "Loss gates recalibrated 2026-07-17 by operator
       decision -- 04-GAP-ANALYSIS.md '## Proposed amendment', routed verbatim ('1') in
       04-09-SUMMARY.md: per-round <= 9.0%, pooled <= 5.0% across all rounds of a run.
       Fixed again BEFORE any amended-gate run, never after one."
       Leave `MIN_DELIVERED_RATIO` (0.85), `CEILING_MIN_ACK_RTT_SAMPLES` (1),
       `EXPECTED_CEILING_PACKETS_PER_FRAME` (8) and the `BASELINE` dict untouched — the
       spike 003 baseline is historical fact, not a threshold.

    2. **Pooled-loss helper.** Add a module-level function
       `_pooled_query_loss_pct(rounds: list[dict[str, Any]]) -> float | None` directly
       above `_evaluate`: sum `queries_lost` across rounds into `lost`, sum
       `queries_ok + queries_lost` across rounds into `total`; return `None` when
       `total == 0`, else `round(100 * lost / total, 2)`. Single source of truth for
       both the gate and the recorded evidence.

    3. **`_evaluate` — pooled check added, per-round logic unchanged.** Keep the
       existing per-round loop byte-for-byte (loss None/over-threshold check,
       `delivered_ratio`, ceiling packet-shape and ack-RTT-sample checks). Replace only
       the final `return True` with: compute `pooled = _pooled_query_loss_pct(rounds)`;
       return False if `pooled is None or pooled > MAX_POOLED_QUERY_LOSS_PCT`; otherwise
       return True.

    4. **`_thresholds()`**: add `"max_pooled_query_loss_pct": MAX_POOLED_QUERY_LOSS_PCT`
       immediately after the `"max_query_loss_pct"` entry, so every results JSON records
       the amended gate it was judged against.

    5. **Results JSON (flagged additive field, gate-neutral).** In `main()`'s success
       branch results dict, add `"pooled_query_loss_pct": _pooled_query_loss_pct(rounds)`
       immediately after `"rounds": rounds`. This field is NOT in the operator wording —
       it is an additive evidence-recording measure consistent with the file's honest
       reporting rules ("every run's actual per-round numbers are written"); flag it as
       such in the summary. It changes no gate logic. Leave the ENV-ERROR branch dict
       unchanged (no rounds ran).

    6. **Module docstring — the written contract must match the code.**
       a. Replace the `tiles` profile description (currently claiming a 0% loss
          criterion) with:

          > tiles   -- ANIM-03: concurrent-query loss stays within the measured
          >            device floor under 20 FPS streaming -- pooled <= 5.0% per
          >            run and <= 9.0% in any single round (blind-fire baseline:
          >            14.6%; recalibrated 2026-07-17 per 04-GAP-ANALYSIS.md
          >            "## Proposed amendment", operator routing in
          >            04-09-SUMMARY.md).

       b. In the "Honest-reporting rules" block, append one rule recording the
          recalibration: "The loss gates were recalibrated once, 2026-07-17, by
          operator decision (04-GAP-ANALYSIS.md verdict H2; routing recorded verbatim
          in 04-09-SUMMARY.md) -- fixed again BEFORE any amended-gate run, never
          after one."
       c. Replace the exit-code contract's `0 -- PASS:` clause (currently requiring
          zero per-round loss) with:

          > 0 -- PASS: every round has query_loss_pct <= MAX_QUERY_LOSS_PCT (9.0)
          >      AND the pooled query loss across all rounds of the run is <=
          >      MAX_POOLED_QUERY_LOSS_PCT (5.0) AND every round has
          >      delivered_ratio >= 0.85. Ceiling profile additionally requires
          >      every sent frame to be exactly 8 packets AND at least 1 ack RTT
          >      sample per round (proves the CopyFrameBuffer probe is being
          >      acked -- assumption A1 evidence).

          The `1 -- FAIL` and `2 -- ENV-ERROR` clauses are already
          threshold-agnostic — leave them unchanged.
       Keep the file's ASCII "--" dash style and Australian English throughout.

    7. **Style gates** (the file's existing bandit `# nosec B105` precedent stays as
       is): `uv run ruff format` then `uv run ruff check` on the file must be clean;
       `uv run pyright .planning/phases/04-animation-flow-control/uat_ack_stream.py`
       must report 0 errors; the pre-commit hooks (ruff-format, ruff, bandit,
       codespell, trailing-whitespace) must pass at commit time.

    Commit with `git commit -s`:
    `feat(04-10): recalibrate uat_ack_stream gate to 9.0% per round + 5.0% pooled`.
    This commit MUST also land before Task 3's evidence commit.
  </action>
  <verify>
    <automated>uv run ruff format --check .planning/phases/04-animation-flow-control/uat_ack_stream.py && uv run ruff check .planning/phases/04-animation-flow-control/uat_ack_stream.py && uv run pyright .planning/phases/04-animation-flow-control/uat_ack_stream.py && uv run python -c "import sys; sys.path.insert(0, '.planning/phases/04-animation-flow-control'); import uat_ack_stream as m; r = lambda loss, ok, lost: {'query_loss_pct': loss, 'delivered_ratio': 0.9, 'queries_ok': ok, 'queries_lost': lost, 'packet_shape_ok': True, 'ack_rtt_samples': 1}; assert m.MAX_QUERY_LOSS_PCT == 9.0 and m.MAX_POOLED_QUERY_LOSS_PCT == 5.0; t = m._thresholds(); assert t['max_query_loss_pct'] == 9.0 and t['max_pooled_query_loss_pct'] == 5.0; assert m._evaluate([r(4.0, 48, 2)] * 3, 'tiles') is True; assert m._evaluate([r(8.0, 46, 4)] * 3, 'tiles') is False; assert m._evaluate([r(10.0, 45, 5), r(0.0, 50, 0), r(0.0, 50, 0)], 'tiles') is False; print('amended gate verified: per-round 9.0 + pooled 5.0')"</automated>
  </verify>
  <acceptance_criteria>
    - `grep -c '^MAX_QUERY_LOSS_PCT: float = 9.0$' .planning/phases/04-animation-flow-control/uat_ack_stream.py` returns 1
    - `grep -c '^MAX_POOLED_QUERY_LOSS_PCT: float = 5.0$' .planning/phases/04-animation-flow-control/uat_ack_stream.py` returns 1
    - The functional gate check passes: three rounds at 4.0% each (pooled 4.0%) evaluates True; three rounds at 8.0% each (per-round pass, pooled 8.0%) evaluates False — proving the pooled gate; one 10.0% round among clean rounds (pooled ~3.3%) evaluates False — proving the per-round gate survived unchanged
    - `_thresholds()` returns both `max_query_loss_pct: 9.0` and `max_pooled_query_loss_pct: 5.0`
    - The module docstring's tiles description and `0 -- PASS:` exit-code clause state the 9.0/5.0 gates (no 0%-loss claim remains anywhere in the docstring), and the honest-reporting rules record the 2026-07-17 operator recalibration
    - ruff format --check, ruff check and pyright all clean on the file; `uv run python .planning/phases/04-animation-flow-control/uat_ack_stream.py --help` exits 0
    - `git diff` for this task touches ONLY uat_ack_stream.py — src/, tests/ and all evidence JSONs untouched; no new dependencies
  </acceptance_criteria>
  <done>The harness enforces the operator-approved amended gate, its written contract (docstring + thresholds JSON) agrees with its code in every location, and the amendment is committed ahead of any run that will be judged by it.</done>
</task>

<task type="auto">
  <name>Task 3: Fresh ANIM-03 headless evidence under the amended gate (3 rounds, Tiles) + STATE gate-note update</name>
  <files>.planning/phases/04-animation-flow-control/04-UAT-TILES.json, .planning/STATE.md</files>
  <read_first>
    - .planning/phases/04-animation-flow-control/uat_ack_stream.py — as amended by Task 2: the exit-code contract this task interprets strictly
    - .planning/phases/04-animation-flow-control/04-CONTEXT.md — Measured baselines table (Tiles at 192.168.19.243 / fallback 192.168.18.62; blind-fire 14.6%, spike ack-gated 0.0%, ack RTT ~98 ms median)
    - .planning/phases/04-animation-flow-control/04-GAP-ANALYSIS.md — table (i) calibration context: at the measured rates the amended gate passes with high probability, but a legitimate FAIL remains possible
    - .planning/STATE.md — the "UAT sequencing gate" entry under Blockers/Concerns that this task updates
  </read_first>
  <action>
    Fixed thresholds for this run — declared here, already committed to history by
    Tasks 1–2, and NEVER adjusted after the run regardless of outcome:
    per-round query_loss_pct ≤ 9.0; pooled query loss ≤ 5.0 across the run's rounds;
    delivered_ratio ≥ 0.85 per round; 3 rounds; 20 FPS; query rate 2/s (harness
    defaults). Confirm before running that both Task 1 and Task 2 commits are in
    history (`git log --oneline -2`) — the amendment must precede the data.

    1. Run: `uv run python .planning/phases/04-animation-flow-control/uat_ack_stream.py
       --ip 192.168.19.243 --profile tiles --rounds 3
       --json-out .planning/phases/04-animation-flow-control/04-UAT-TILES.json`
       (~3 × 30 s of streaming plus setup). This intentionally overwrites the
       miscalibrated-gate evidence; the superseded bytes remain at commit 5de88f6 and
       must be cited in the summary as the superseded evidence.
    2. Exit 2 (ENV-ERROR) on the primary: retry ONCE with `--ip 192.168.18.62`
       (fallback on ENV-ERROR only — never on FAIL). If both IPs return ENV-ERROR:
       commit the recorded ENV-ERROR JSON, leave STATE's gate note in place with the
       outcome appended, and STOP — surface the blocker to the operator; never
       fabricate, never stream at an absent device.
    3. Exit 1 (FAIL): a genuine signal under the operator's own amended gate. First
       preserve the evidence: copy the JSON to
       .planning/phases/04-animation-flow-control/04-UAT-TILES-amended-run1-FAIL.json.
       Then re-run ONCE with identical flags to rule out a transient network event
       (04-06 Task 1 precedent — exactly one re-run, no more). If the re-run also
       FAILs: commit BOTH JSONs, flag loudly in the summary with per-round AND pooled
       figures, leave the ANIM-03 checkbox unchecked, leave 04-06 Task 2 and 04-07
       gated, and update the STATE gate note to say the amended gate itself FAILed and
       a fresh operator routing is required (`/gsd-plan-phase 4 --gaps` with the new
       evidence). The thresholds are NEVER adjusted — a FAIL under the amended gate is
       a new operator decision, not an executor edit.
    4. Exit 0 (PASS): record in the summary, per round and pooled: query loss (vs the
       9.0/5.0 gates and the 14.6% blind-fire baseline), delivered ratios (vs 0.85),
       query latency medians, ack RTT median/p95 (vs ~98/150 ms spike reference), and
       the top-level pooled_query_loss_pct the amended harness now writes.
    5. Update .planning/STATE.md — replace the body of the "UAT sequencing gate" bullet
       under Blockers/Concerns to reflect the actual outcome. On PASS, use:

       > **UAT sequencing gate (updated <today>, 04-10 complete):** the operator-approved
       > ANIM-03 recalibration is applied (ROADMAP criterion 3, REQUIREMENTS ANIM-03,
       > uat_ack_stream.py per-round ≤ 9.0% + pooled ≤ 5.0%; commits in 04-10-SUMMARY.md)
       > and the fresh headless run PASSed under the amended gate (04-UAT-TILES.json,
       > exit 0). 04-06 Task 1 is superseded by that run; 04-06 Task 2 (Tiles visual
       > checkpoint — the single remaining ANIM-03 item, operator asked exactly once) is
       > now UNGATED and executes next via `/gsd-execute-phase 4` resuming 04-06. 04-07
       > (Ceiling) unblocks after 04-06 Task 2 approval, inheriting the amended shared
       > gate. ANIM-03's REQUIREMENTS checkbox flips only at 04-06 Task 2 completion.

       On FAIL or ENV-ERROR, keep the bullet's gate in force and state the honest
       outcome and the routing from steps 2–3 instead. Do NOT mark ANIM-03 complete in
       REQUIREMENTS.md in ANY outcome — completion is owned by 04-06 Task 2
       (04-05/04-08/04-09 precedent).
    6. Commit evidence + STATE together with `git commit -s`:
       `test(04-10): re-establish ANIM-03 headless evidence under the amended gate`
       (adjust the verb honestly on FAIL/ENV-ERROR, e.g. "record ANIM-03 amended-gate
       FAIL evidence"). This commit must be strictly after the Task 1 and Task 2
       amendment commits.
  </action>
  <verify>
    <automated>test -f .planning/phases/04-animation-flow-control/04-UAT-TILES.json && uv run python -c "import json; d = json.load(open('.planning/phases/04-animation-flow-control/04-UAT-TILES.json')); t = d['thresholds']; assert t['max_query_loss_pct'] == 9.0 and t['max_pooled_query_loss_pct'] == 5.0, t; assert d['outcome'] in ('PASS', 'FAIL', 'ENV-ERROR'), d; print('amended-gate evidence on disk:', d['outcome'])"</automated>
  </verify>
  <acceptance_criteria>
    - 04-UAT-TILES.json exists with real measured per-round data (or a recorded ENV-ERROR outcome), and its `thresholds` block records `max_query_loss_pct: 9.0` and `max_pooled_query_loss_pct: 5.0` — proof the run was judged by the amended gate, never placeholder numbers
    - The exit code, its meaning, per-round loss, the pooled loss figure, delivered ratios and ack RTTs are recorded in the summary against the amended gates and the 14.6% blind-fire baseline; the superseded miscalibrated-gate evidence is cited at commit 5de88f6
    - `git log` shows the Task 1 and Task 2 amendment commits strictly BEFORE this task's evidence commit
    - On FAIL: 04-UAT-TILES-amended-run1-FAIL.json preserved before the single re-run; both outcomes committed; thresholds untouched; honest FAIL routing recorded (new operator routing required, 04-06 Task 2/04-07 stay gated)
    - STATE.md's "UAT sequencing gate" bullet reflects the actual outcome, including (on PASS) that 04-06 Task 2 is the ungated sole visual-checkpoint owner and 04-07 unblocks behind it
    - REQUIREMENTS.md ANIM-03 checkbox remains unchecked in every outcome; src/ and tests/ untouched across all three tasks
  </acceptance_criteria>
  <done>ANIM-03's headless evidence is re-established honestly under the operator-approved amended gate — PASS clearing the way for 04-06 Task 2 as the final ANIM-03 item, or a loudly-flagged FAIL/ENV-ERROR routed back to the operator — with the amendment provably fixed in history before the data.</done>
</task>

</tasks>

## Sequencing after this plan (gate resolution)

1. **04-10 (this plan)** applies the amendment and owns the amended-gate headless
   re-run, superseding 04-06 Task 1's FAIL evidence (which stays in history at
   5de88f6 — nothing is rewritten).
2. **On headless PASS → 04-06 resumes at Task 2 only** (`/gsd-execute-phase 4`): the
   operator visual-smoothness checkpoint, the single time the operator is asked for
   the Tiles visual verdict, run against the IP this plan's Task 3 actually used.
3. **ANIM-03 completes** only when both exist: this plan's amended-gate headless PASS
   AND the 04-06 Task 2 operator approval. The REQUIREMENTS checkbox and Traceability
   row flip at 04-06 Task 2 completion — owned by 04-06's execution, not this plan.
4. **04-07 (Ceiling, ANIM-04 hardware evidence)** unblocks after 04-06 completes, per
   its existing `depends_on`, inheriting the amended shared harness gate (annotated in
   Task 1).
5. **On headless FAIL/ENV-ERROR**: 04-06 Task 2 and 04-07 stay gated; the outcome
   routes back to the operator for a fresh decision — thresholds are never adjusted
   by executors.

## Artifacts this plan produces

| Artifact | Kind | Location |
|----------|------|----------|
| Amended ROADMAP Phase 4 success criterion 3 (operator-approved wording) | Governing document amendment | `.planning/ROADMAP.md` |
| Amended REQUIREMENTS ANIM-03 loss clause (operator-approved wording, checkbox unchecked) | Governing document amendment | `.planning/REQUIREMENTS.md` |
| Supersession annotations on the stale 0%-gate criteria | Plan annotations | `.planning/phases/04-animation-flow-control/04-06-PLAN.md`, `04-07-PLAN.md` |
| Amended harness: 9.0% per-round + 5.0% pooled gates, self-consistent docstring contract and thresholds JSON | UAT instrument amendment | `.planning/phases/04-animation-flow-control/uat_ack_stream.py` |
| Fresh amended-gate headless evidence (3 rounds, or honest FAIL/ENV-ERROR; FAIL first-attempt preserved as `04-UAT-TILES-amended-run1-FAIL.json`) | Evidence artefact | `.planning/phases/04-animation-flow-control/04-UAT-TILES.json` |
| Updated "UAT sequencing gate" note reflecting the post-run state | State record | `.planning/STATE.md` |

<threat_model>
## Trust Boundaries

| Boundary | Description |
|----------|-------------|
| Harness → LAN Tiles | Outbound streaming + query load against user-owned quiesced test devices (192.168.19.243 primary, 192.168.18.62 fallback) |
| Operator decision → repo gates | An approved recalibration wording is transcribed into gate-bearing documents and code |

## STRIDE Threat Register

| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
| T-04-20 | DoS (outbound, self-inflicted) | 20 FPS stream × 3 rounds (+ at most one transient-rule-out re-run) | low | mitigate | Fixed rounds/duration; the shipped gate throttles under congestion; ENV-ERROR reachability guard prevents streaming at an absent device; fallback IP tried only on ENV-ERROR |
| T-04-21 | Tampering / Repudiation | Gate recalibration (thresholds in ROADMAP/REQUIREMENTS/harness) | medium | mitigate | Wording applied verbatim from the operator-approved "## Proposed amendment" with the routing decision recorded verbatim in 04-09-SUMMARY.md; amendment commits strictly precede the evidence commit (protocol-fixed-before-data, 04-08 discipline); docstring/thresholds-JSON kept consistent so the written contract cannot disagree with the code; thresholds never adjusted post-run |
| T-04-22 | Repudiation | Overwriting 04-UAT-TILES.json with amended-gate evidence | low | mitigate | Superseded miscalibrated-gate bytes preserved in git history (commit 5de88f6) and cited in the summary; any new FAIL preserved under the distinct filename 04-UAT-TILES-amended-run1-FAIL.json before the single re-run |

No packages are installed by this plan (zero-dependency project) — no supply-chain gate applies.
</threat_model>

<verification>
- ROADMAP criterion 3 and REQUIREMENTS ANIM-03 carry the operator-approved wording; no live document still demands the 0% gate
- uat_ack_stream.py enforces 9.0% per-round + 5.0% pooled with docstring/_thresholds() agreement, ruff/pyright clean; functional gate check proves both gates fire independently
- Fresh 04-UAT-TILES.json on disk whose thresholds block reads 9.0/5.0, outcome recorded exactly as the exit code said
- Amendment commits precede the evidence commit in `git log`; `git diff` across all plan commits touches no src/ or tests/ paths and installs nothing
- 04-06/04-07 annotated (not rewritten); STATE gate note updated; ANIM-03 checkbox still unchecked pending 04-06 Task 2
</verification>

<success_criteria>
- The operator's recalibrate decision is fully applied — verbatim wording in both governing documents and the harness, with a consistent written contract — and ANIM-03's headless evidence is re-established honestly under the amended gate, with exactly one contradiction-free path forward: 04-06 Task 2 (sole visual-checkpoint owner) then 04-07
</success_criteria>

<output>
Create `.planning/phases/04-animation-flow-control/04-10-SUMMARY.md` when done, recording:
the amendment commits and their order relative to the evidence commit, any flagged
deviations from the verbatim wording (expected: ROADMAP single-line reflow; additive
pooled_query_loss_pct results field), the run's exit code with per-round + pooled figures
vs the amended gates and the 14.6% baseline, the 5de88f6 supersession citation, and the
updated STATE gate note.
</output>
