Skip to content

G3 #1 blocker: is_live_view freeze/lockout from multi-campaign divergence (3/5 personas) #640

Description

@100yenadmin

Found by the 2026-06-03 VM 5-persona sweep on the built .app (build 0cc9856). The #1 cross-persona blocker holding G3-satisfaction at 5.6 (need ≥7) — hit by newbie, narrative, adversarial (3/5).

Symptom

Mid-session the Chronicle freezes (player's last action shows, no DM narration appears) and all action buttons disable with tooltip "viewing non-live campaign" / textbox "Read-only: viewing non-live campaign" — a permanent lockout with no recovery. The newbie waited 4+ min; narrative saw "Composing… completes but no new beat."

Root cause (evidence-backed)

The play surface gates can_act = live AND is_live_view, where is_live_view = live AND viewed_campaign_id == attached_campaign_id (viewer/server.py:5717, 6419). It latches False because the run accumulates multiple campaigns:

  • vm2-newbie-b had 4 campaigns: 1 stale (camp_a60121, 2026-06-02) + 3 today (camp_5db4e1 09:04, camp_4721ad 09:06, camp_29656 09:07).
  • play_party.sh mints a fresh campaign on every launch (scripts/play_party.sh:183, start_world), and part-A (.app RESUME→PLAY native bridge) + part-B (harness play_party.sh, qa/ui_playtest_app.sh:566) each start a play session → ≥2 campaigns; plus uncleaned stale leftovers.
  • The viewer attaches by recency to the newest (camp_29656, day 2) while the DM's backend ran a different one (camp_4721ad, day 1) → divergence.
  • The viewer's self-healing (_live_play_view_campaign, viewer/server.py:6347-6389) deliberately refuses to follow a "parallel store" (>1 current+live campaign) to avoid misrouting writes → no recovery → permanent lockout.

This is the same can_act-wedge family as #35/#564 (single-flight)/#566 (retry-reuse).

Fix options (needs design — load-bearing state surface)

  1. Single-flight the campaign across part-A/part-B: part-B play_party.sh REUSES the .app RESUME's already-live campaign instead of start_world-minting a fresh one (the cleanest — one campaign, no divergence).
  2. Clean the play-state campaigns dir at the start of each persona run (remove stale + cross-run) so only the cold-open's campaign exists.
  3. Viewer follows the move-sink's ACTUAL active campaign (the one the DM is writing beats to), not recency — disambiguating the parallel store by DM activity.

Recommend (1)+(2): prevent the multiple campaigns at the source; keep the viewer's conservative self-heal.

Acceptance

A fresh qa/ui_playtest_app.sh run produces exactly one play-store campaign; /session-surface reports is_live_view=true + can_act=true throughout; no "viewing non-live campaign" lockout; newbie/narrative/adversarial chronicles advance every beat. Verified on the NEXT built .app sweep (non-reproduction).

Metadata

Metadata

Assignees

No one assigned

    Labels

    severity:minorPolish gap — release-quality wouldn't ship with it

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions