diff --git a/CALL_TO_PLAY_REVIEW_FABLE_5_XHIGH.md b/CALL_TO_PLAY_REVIEW_FABLE_5_XHIGH.md
new file mode 100644
index 0000000..47a16f1
--- /dev/null
+++ b/CALL_TO_PLAY_REVIEW_FABLE_5_XHIGH.md
@@ -0,0 +1,40 @@
+# Call to Play review — Fable 5 (xhigh)
+
+## Verdict
+
+The plan is faithfully implemented — all five commits match the planned sequence, scope, and invariants, and the full acceptance suite passes on my machine: workspace tests (189), frontend tests (26), clippy, fmt, `git diff --check`, frontend `tsc`, and the docker scenarios S48 and S49. The few deviations from the plan's letter are genuine improvements. I found no correctness bugs. There is one architectural edge case worth knowing about (self-healing, arguably by design) and one real UX friction point.
+
+## a) Plan fidelity
+
+Each plan bullet traced to code:
+
+- **Atomic merge** — `merge_batch_at` (`call_to_play.rs:83`) validates the whole batch, dedups, detects ID conflicts, evaluates tombstones/rootedness against retained-plus-batch, compacts exactly once, and only then commits. Store-unchanged-on-error is tested for both invalid and conflicting batches. `Create + AddTime` revival is tested in both input orders.
+- **Acknowledged delivery** — `PROTOCOL_VERSION` 7, `CallToPlayAck` with all six planned outcomes, request/response in `send_call_to_play_events`, and the resync matrix in `deliver_to_peer`/`delivery_resync_reason` exactly matches the plan: transport failure/malformed/NeedHandshake/NeedHistory → one Hello resync; Rejected → logged, non-retriable; Applied/Duplicate/Obsolete → done. The IP check is gone; identity is roster membership + envelope==actor, and ARCHITECTURE.md now states plainly that this is not hostile-peer authentication.
+- **Terminal retention** — 15-minute full-history window, then tombstone-for-session, separate 5-minute recovery window for unresolved calls, frontend `running`/`cancelled` states with `terminalAt`, badge exclusion, sort-last, disabled composer/controls, raw-event pruning, and doc updates. S49 proves late-joiner reconstruction of a terminal call.
+- **Deadline extension** — `extendDeadline = max(now, deadline) + 5min`, card passes `nomination.deadline`, tested for both the early-ready and overdue cases.
+- **Startup message** — the Tauri command returns `Ok(false)` only for uninitialized peer core and `Err(store reason)` otherwise, and the hook maps these to the four distinct messages without marking transport unavailable for store errors. The connecting message self-clears once the 2-second snapshot poll succeeds.
+
+**Deviations, all justified:**
+
+1. The plan said "rebuild event IDs from retained events." The implementation went further and **deleted the separate ID set entirely** — dedup scans retained history directly. This makes the "IDs correspond only to retained events" invariant structurally impossible to violate rather than merely maintained. Better than the plan.
+2. "Always permit Start and Cancel at the cap" is implemented as a generalization: the 4,096 cap counts only *unresolved* events (`unresolved_event_count`), so a terminal action inherently passes because it resolves the call, and settled histories/tombstones never consume active capacity. Cleaner than special-casing two action types, and both behaviors are tested.
+3. A nice detail beyond the plan: a pre-terminal chat message arriving *after* the call went terminal still merges into the read-only display during the 15-minute window (the obsolete check compares against the terminal event's order key, not mere terminal existence). That's consistent with "every visible call has its entire history."
+
+## b) Architecture
+
+The design holds up well. `merge_batch` is now the single choke point for every mutation path — local publish, live delivery, and handshake all flow through one atomic validate → dedup → apply → compact operation. That is exactly the seam the findings pointed at, and collapsing findings 2, 3, and 5 into it was the right call. Convergence comes from a grow-only deduplicated event set plus deterministic compaction, with no consensus machinery — appropriate for a trusted LAN.
+
+Two observations, neither blocking:
+
+- **Rootless tombstones don't propagate.** The "missing-root actions are not retained alone" invariant applies to Start/Cancel too, so a peer that joins *after* a call's 15-minute window never stores the creator's tombstone (its handshake merge returns `NeedHistory`, which in the handshake path only logs). If a third peer that slept through the finish later hands that fresh peer the stale active history, the finished call briefly resurrects on the fresh peer until its next handshake with any tombstone-holder roots the call and applies the tombstone. It self-heals and requires an unusual sequence (long-deadline scheduled call + offline peer + fresh joiner), so I think the trade-off is fine — but be aware of it, and note the secondary symptom: the fresh peer logs a "handshake omitted roots" warning on every handshake with a tombstone-holder for the rest of the session. If that log noise bothers you, downgrading that specific case to debug would be cheap.
+- **The backend `HistoryIndex` and the frontend reducer are parallel implementations** of the same semantics (creator authority, `(at, id)` ordering, earliest-terminal-wins, latest-extension-wins). I checked them against each other and they agree today, including the subtle cases (forged terminal by non-creator, extension ordering, pre-create actions). This duplication is inherent to having a Rust store and a TS presentation reducer, but it's the seam most likely to drift — any future rule change must land in both `call_to_play.rs` and `callToPlay.ts`.
+
+Minor: `merge_batch` clones the full store per call, so a live event costs O(n) — irrelevant under the 4,096 cap, just don't raise the cap by 100× without revisiting.
+
+## c) User perspective
+
+The lifecycle is now genuinely intuitive. "Time's up" being unresolved-but-recoverable, and "Running" being an explicit success receipt that only the creator's Start can produce, is a real conceptual improvement — deadline passage never silently claims a game happened. The ticker ordering supports this: Time's up ranks *first* (it needs the creator's attention), terminal receipts sink to the bottom in muted colors and don't inflate the badge. "Add 5 more minutes" finally does what it says. The startup message no longer sends users hunting for a game-folder problem that doesn't exist.
+
+One real friction point: **when a call starts, participants get no launch affordance.** The creator's "Start now" auto-launches locally, but everyone else's card flips to a read-only "X is running." note — at precisely the moment they all need to launch the game, they must close the overlay and find it in the library. The plan specified terminal cards as read-only, so this is faithful — but a "Launch" button on the Running card (for participants who have the game installed) would remove the most awkward step in the happy path. Worth a follow-up commit if you agree.
+
+Two nits: `design/launcher/SPEC.md` still describes the ticker sort as "ready → starting-soon → the rest (TICKER_RANK = ready 0, soon 1…)" while the code ranks `expired` first — that mismatch predates this branch, but since the spec section was touched anyway it could have been corrected. And the ticker's "waiting to start" line for Ready calls doesn't say *who* everyone is waiting for, while the card note does name the creator — a tiny inconsistency, fine as is.
diff --git a/CALL_TO_PLAY_REVIEW_GEMINI_3.6_FLASH.md b/CALL_TO_PLAY_REVIEW_GEMINI_3.6_FLASH.md
new file mode 100644
index 0000000..a2072e6
--- /dev/null
+++ b/CALL_TO_PLAY_REVIEW_GEMINI_3.6_FLASH.md
@@ -0,0 +1,96 @@
+# Call to Play Code & Architecture Review Report
+
+I have conducted a thorough review of the commits (`e141229` through `2c204ac`) on branch `calltoplay`, referencing [`FABLE_5_FINDINGS.md`](file:///pantheon/pfs/git/rust/pfs/lanspread/FABLE_5_FINDINGS.md) and [`CALL_TO_PLAY_FIXES_PLAN.md`](file:///pantheon/pfs/git/rust/pfs/lanspread/CALL_TO_PLAY_FIXES_PLAN.md).
+
+---
+
+## 1. Plan Implementation & Deviation Assessment
+
+The plan outlined in [`CALL_TO_PLAY_FIXES_PLAN.md`](file:///pantheon/pfs/git/rust/pfs/lanspread/CALL_TO_PLAY_FIXES_PLAN.md) is **faithfully and elegantly implemented across all 5 code commits**, with zero regression to core invariants.
+
+| Commit | Scope | Plan Requirements | Code Verification | Status |
+|---|---|---|---|---|
+| [`be7ad2e`](file:///pantheon/pfs/git/rust/pfs/lanspread/crates/lanspread-peer/src/call_to_play.rs) | Atomic Batch Merge & Store Semantics | - Transactional history merge
- Single-pass $O(N)$ compaction
- Conflicting ID batch rejection
- `NeedHistory` for missing roots
- Rebuild event IDs from retained events
- Capacity cap applies to unresolved history only | - `merge_batch_at` in [call_to_play.rs](file:///pantheon/pfs/git/rust/pfs/lanspread/crates/lanspread-peer/src/call_to_play.rs#L83)
- `deduplicate_batch`
- `unresolved_event_count`
- Tests cover revival & conflict behavior | **Faithful** |
+| [`e5d70ae`](file:///pantheon/pfs/git/rust/pfs/lanspread/crates/lanspread-peer/src/services/stream.rs) | Live Replication & Acknowledgement | - Protocol v7 bump
- `CallToPlayAck` return outcomes
- Remove unreliable source-IP equality check
- Enforce envelope vs actor ID matching
- Async handshake resync on delivery failure | - Protocol updated in [`lanspread-proto`](file:///pantheon/pfs/git/rust/pfs/lanspread/crates/lanspread-proto/src/lib.rs)
- `handle_call_to_play_events` in [stream.rs](file:///pantheon/pfs/git/rust/pfs/lanspread/crates/lanspread-peer/src/services/stream.rs#L124)
- `delivery_resync_reason` in [call_to_play.rs](file:///pantheon/pfs/git/rust/pfs/lanspread/crates/lanspread-peer/src/call_to_play.rs#L472) | **Faithful** |
+| [`9c34efa`](file:///pantheon/pfs/git/rust/pfs/lanspread/crates/lanspread-tauri-deno-ts/src/lib/callToPlay.ts) | Terminal Outcome Retention | - 15-minute terminal outcome display (`TERMINAL_RETENTION_MS`) for `Running` and `Cancelled`
- Post-15-min compaction to tombstones
- Frontend status reduction, sorting, and badge exclusion
- Peer CLI scenario S49 | - `compact_history` in [call_to_play.rs](file:///pantheon/pfs/git/rust/pfs/lanspread/crates/lanspread-peer/src/call_to_play.rs#L340)
- Reducer logic in [callToPlay.ts](file:///pantheon/pfs/git/rust/pfs/lanspread/crates/lanspread-tauri-deno-ts/src/lib/callToPlay.ts)
- Roster/chat retained | **Faithful** |
+| [`8d3affe`](file:///pantheon/pfs/git/rust/pfs/lanspread/crates/lanspread-tauri-deno-ts/src/lib/callToPlay.ts) | Extend from Current Deadline | - `extendDeadline`: $\max(\text{now}, \text{currentDeadline}) + \text{duration}$ | - Implemented in `extendDeadline` in [callToPlay.ts](file:///pantheon/pfs/git/rust/pfs/lanspread/crates/lanspread-tauri-deno-ts/src/lib/callToPlay.ts#L12) | **Faithful** |
+| [`2c204ac`](file:///pantheon/pfs/git/rust/pfs/lanspread/crates/lanspread-tauri-deno-ts/src/hooks/useCallToPlay.ts) | Startup & Error Guidance | - Replace missing folder prompt with LAN connecting state
- Map specific store errors (`Obsolete`, `NeedHistory`, `HistoryFull`) to human guidance | - Implemented in `useCallToPlay.ts` and `callToPlayPublishErrorMessage` | **Faithful** |
+
+### Implementation Refinements Over the Initial Plan
+1. **Tombstone Representation**: Rather than instantiating a separate tombstone data structure, `compact_history` retains **only** the `Start` or `Cancel` event (`event.id == terminal.event_id`) after the 15-minute terminal retention window expires. `terminal_tombstone_call_ids` uses unrooted terminal events to reject any incoming obsolete history. This is cleaner and more memory-efficient than allocating explicit tombstone markers.
+2. **Unresolved History Cap (`unresolved_event_count`)**: The active event cap ($4,096$) is enforced strictly against *unresolved* calls (`Create` without `Start`/`Cancel`). As soon as a creator emits `Start` or `Cancel`, that call's events no longer count against the active cap. This guarantees a user can always settle (Start or Cancel) an open call even when the store is full.
+
+---
+
+## 2. Holistic Architecture Review
+
+```
+ +------------------------+
+ | Frontend (TS/Tauri) |
+ | Event Reducer & Hooks |
+ +-----------+------------+
+ | publish_call_to_play / snapshot
+ v
+ +------------------------+
+ | CallToPlayStore |
+ | (Atomic Batch Merge) |
+ +-----------+------------+
+ |
+ +-------------------+-------------------+
+ | (Local immediate) | (Async broadcast)
+ v v
+ +-------------------+ +-------------------+
+ | Local UI Emitter | | Peer QUIC Stream |
+ +-------------------+ | (Protocol Ver 7) |
+ +---------+---------+
+ | CallToPlayEvents
+ v
+ +-------------------+
+ | CallToPlayAck |
+ | (Applied/NeedHist)|
+ +-------------------+
+```
+
+### Architectural Soundness
+1. **Event-Sourced LAN Replication vs Server-Authoritative State**:
+ Maintaining an event-sourced replication model with deterministic reduction is optimal for LAN Spread. Decentralized peer-to-peer LAN parties lack guaranteed central servers. Using atomic batch merges ($O(N)$ compaction) completely eliminates the $O(N^2)$ quadratic slowdown of the previous per-event insertion model.
+
+2. **Network Identity Model**:
+ Removing source-IP equality comparisons fixes a major real-world bug on multi-homed hardware (Ethernet + Wi-Fi / VPN / virtual bridges). Validating that `envelope peer_id` is present in the mDNS peer roster and verifying `event.actor_id == envelope peer_id` accurately matches the trusted-LAN threat model without making false cryptographic guarantees.
+
+3. **Asynchronous Healing**:
+ Local updates succeed instantly for the local user without blocking on network delivery (`task_tracker.spawn(...)`). If a remote peer rejects an event with `NeedHistory` or `NeedHandshake`, an asynchronous full `Hello`/`HelloAck` resync is scheduled. This isolates local UI responsiveness from network transport delays.
+
+4. **Lifecycle & Memory Management**:
+ The 3-tier lifecycle (`Open` $\rightarrow$ `Time's up` [5-min recovery] $\rightarrow$ `Running`/`Cancelled` [15-min display] $\rightarrow$ `Tombstone`) strikes the right balance between retaining full chat/roster history for late joiners and preventing unbounded memory growth.
+
+---
+
+## 3. User Experience (UX) Analysis
+
+```
+ UX Flow Comparison (Add Time Action)
+
+ BEFORE: [10-min Call] -- (Filled at min 2) --> Click "+5 mins" --> Deadline set to (2+5) = 7 mins! (SHORTENED!)
+ AFTER: [10-min Call] -- (Filled at min 2) --> Click "+5 mins" --> Deadline set to max(2, 10)+5 = 15 mins! (EXTENDED!)
+```
+
+1. **Intuitive "+5 minutes" Extension**:
+ - *Previous behavior*: Setting deadline to `now + 5` inadvertently shortened calls that reached capacity early.
+ - *Current behavior*: `Math.max(now, currentDeadline) + 5` preserves existing remaining time when extending early, and correctly grants 5 new minutes to an overdue call.
+
+2. **Startup & Connection Guidance**:
+ - *Previous behavior*: Attempting an action during startup raised misleading errors about missing game folders.
+ - *Current behavior*: Shows `"Call to Play is still connecting to the LAN. Try again in a moment."` while `actorId` is initializing, clearing automatically upon connection.
+
+3. **Clear Terminal Receipts (`Running` and `Cancelled`)**:
+ - *Previous behavior*: Starting or canceling a call caused it to disappear or act erratically, hiding game chat.
+ - *Current behavior*: `Running` displays as a clear green success receipt card, and `Cancelled` displays as a read-only historical card. Roster and chat remain accessible for 15 minutes, sorted below active calls and excluded from badge counts.
+
+---
+
+## Conclusion & Recommendation
+
+The commits are **clean, robust, and fully faithful to the findings and plan**. The architectural choices are sound for a LAN environment, and the UX is intuitive and frictionless.
+
+No further code changes are needed; the implementation is ready for merge.
diff --git a/CALL_TO_PLAY_REVIEW_KIMI_K3.md b/CALL_TO_PLAY_REVIEW_KIMI_K3.md
new file mode 100644
index 0000000..a855f86
--- /dev/null
+++ b/CALL_TO_PLAY_REVIEW_KIMI_K3.md
@@ -0,0 +1,46 @@
+# Review: Call to Play fix series (`872692e..2c204ac`)
+
+## a) Faithfulness to the plan — high, with only minor test-plan gaps
+
+**Commit mapping is 1:1 with the planned sequence**, same titles, and every bullet lands:
+
+| Plan | Implementation | Verdict |
+|---|---|---|
+| 1. Atomic merge | `merge_batch` validates + dedups the whole batch, conflicts reject without mutation (`self.events` only assigned at the end), one post-merge compaction, IDs derived from retained events, orphan actions → `missing_call_ids`, Create+AddTime revives in any order (tested both orders), `applied` = only what survives compaction, cap counts only unresolved events so Start/Cancel always settle | ✓ Faithful |
+| 2. Acknowledged replication | `PROTOCOL_VERSION` 6→7, `CallToPlayAck` with exactly the six planned outcomes, IP check removed, roster + actor-matches-envelope checks, NeedHandshake/NeedHistory/transport/malformed → one Hello/HelloAck resync, Applied/Duplicate delivered, Obsolete finished, Rejected logged non-retriable, publication spawns deliveries into `task_tracker` and returns after local merge (verified: `join_all` is inside the spawned task, so an offline peer can't block the UI), ARCHITECTURE.md documents the trust model honestly | ✓ Faithful |
+| 3. Terminal retention | 15-min full terminal histories in snapshots, then tombstone-only for the session; unresolved Time's-up evicted as a unit after 5 min; `running`/`cancelled` states with `terminalAt`; ticker/overlay rendering, sorted last, excluded from badge; unknown-game degraded rendering kept; frontend raw-map pruning; SPEC.md + ARCHITECTURE.md updated incl. clock-skew assumption; S49 added | ✓ Faithful |
+| 4. Extend from current deadline | `max(now, deadline) + 5min`, overdue case preserved, terminal calls non-extendable (controls unrendered) | ✓ Faithful |
+| 5. Startup message | Exact planned wording, store errors no longer mark transport unavailable, four distinct messages (startup / obsolete / full / unexpected), connecting message auto-clears once the peer is ready | ✓ Faithful |
+
+All six Fable-5 findings are addressed, and every invariant in the plan's list verifiably holds in the final code. The "merge histories atomically" fix correctly treats findings 2+5 as one problem, as the findings demanded.
+
+**Test-plan gaps (minor):**
+1. **No end-to-end test of the triggered heal.** The plan's "NeedHistory, NeedHandshake, and lost acknowledgements trigger idempotent handshake healing" is only covered at the `delivery_resync_reason` decision level. S48/S49 prove handshake-based reconstruction, but nothing drives an orphan-AddTime → NeedHistory → resync → revival sequence. Understandable (needs 5-min waits or clock mocking), but it's a real gap against the plan's own list.
+2. **"Terminal controls and chat composer are disabled"** has no automated test (frontend tests are lib-level only; there is no component-test infrastructure). Verified by inspection instead.
+3. **"Fix snapshot waiting via the direct reply path"** turned out to be a no-op — the CLI's `call_to_play_events` already polls through the reply channel. Justified deviation.
+
+**Nits:** `known_peer_id_accepts_live_events_without_transport_ip_matching` is somewhat vacuous (the function no longer takes `remote_addr`, so IP mismatch can't even be expressed) — harmless as intent documentation. `callToPlayPublishErrorMessage` substring-matches backend error strings — a brittle coupling, though the tests pin the current wording.
+
+## b) Architecture — sound choices throughout
+
+- **Derived event IDs + tombstones** is the elegant part: the ID set is bounded by construction, revival works, resurrection is blocked, and finding 2's "retained IDs correspond to retained events and deliberate terminal tombstones" is literally realized.
+- **Fail-closed batch rejection** (invalid/conflict → store untouched) is the right default under the trusted-LAN model. The trade-off — one bad event voids an entire handshake heal — is practically unreachable: IDs are UUIDs and validation is deterministic and identical sender-side.
+- **Retention symmetry is the quiet win:** backend compaction and frontend derivation use the same constants (5/15 min) keyed off *event timestamps*, not receipt times. All peers converge on identical visibility with zero extra protocol, and the S49 late-joiner case falls out naturally.
+- **Ack semantics map 1:1 onto merge outcomes**, and the heal is idempotent (lost ack → resync → duplicate). Not rebroadcasting live events keeps it loop-free. The identity story is now honest: roster + actor match, documented as not-authentication.
+- **Capacity on unresolved history only** fixes the "Start at cap" deadlock and keeps settled calls from pressuring new ones. Tombstones accumulate one small event per finished call per session — negligible and deliberate.
+- The handshake receiver *logging* missing roots rather than re-requesting is correct — the handshake is itself the heal, and re-requesting would loop.
+
+## c) User experience — a genuine improvement; lifecycle finally coherent
+
+The old flow had two genuinely weird behaviors: a started call vanished after **3 seconds**, and a cancelled call vanished **instantly** — mid-conversation, for everyone. The new flow (Open/Ready → Time's up, recoverable → Running/Cancelled receipts for 15 min → retired) matches how a LAN party actually works: "who's playing what right now?" is answerable at a glance, receipts sort last, stay out of the badge, and chat remains readable. "Time's up" vs "Running" being distinct states (unresolved vs final receipt) is the right call — deadline passage never implies the game started. Add-time now does what its label says, the startup message no longer blames the wrong cause, and error messages are specific and actionable ("Start or cancel an active call, then try again").
+
+**Residual friction, in decreasing order of importance:**
+
+1. **First-run users see "still connecting… try again in a moment" forever.** The peer only starts via `update_game_directory`, so without a game folder there is no "moment" after which it connects. Finding 6 explicitly wanted folder guidance *reserved for a known missing-folder condition* — the plan narrowed that to just the connecting message and the implementation follows the plan, so this is faithful, but the finding's full intent isn't realized. The frontend already has `hasGameDirectory` in `useGameDirectory`; wiring it into the Call to Play error path would close this cheaply. This is the one place where deviating from the plan would have been justified.
+2. **Terminal ticker rows render `MiniBubbles` with live "ready in Xm" countdown tags** for participants whose `readyAt` hadn't elapsed — a Running receipt with ticking countdowns looks slightly alive when it's meant to be a receipt.
+3. **Terminal cards render empty roster slots up to maxPlayers** — on a Cancelled receipt, empty slots can read as "seats still open".
+4. Pre-existing, out of scope: the badge counts Time's-up calls for everyone, though only the creator can act on them.
+
+## Verdict
+
+Approve. The plan is implemented faithfully and, where it matters (derived IDs, atomic merge, ack-driven healing, retention symmetry), the execution is as good as or better than the plan described. Architecture and UX are coherent. Before merging I'd only consider: (1) the missing-folder special case, since the finding called for it and the data is already available in the frontend, (2) the two cosmetic terminal-receipt nits, and (3) noting the untested heal loop as a known coverage gap — none of which are blockers.