Root cause (adversarially verified, RRI 2026-06-09)
The shared engine-root of two critical bugs — the hero-bind party-wipe (vm2-opuslean-narr2) and the input-lock/no-spinner (vm2-newbie). When a cold-open beat times out (timeout(1) rc=124, >WORLDOS_COLDOPEN_TIMEOUT 400s), play.sh's one-shot retry re-mints only the --session-id and re-invokes the identical cold-open prompt — which again instructs start_world("<world>"). The engine has no resume context, so it mints a SECOND, fresh campaign (party-less — load_canon_character never ran), orphaning attempt-1's seated run. The viewer then auto-follows the newer empty orphan → "wiped" party + a frozen empty screen.
PR #717 mitigates the read-side (the viewer demotes party-less orphans), and #718 adds the cold-open spinner — but the orphan is still created (a wasted cold-open + a data-loss footgun). This issue is the engine-side prevention.
Fix (additive, invariant-safe; engine stays sole writer)
In scripts/play.sh dm_turn()'s retry block (~287-296): before re-invoking _dm_invoke on a cold-open (first=1) turn with no HERO_CAMP, detect the campaign attempt-1 already minted — clawdnd_live_campaign_id "$ROOT" "$STATE_DIR" "$WORLD" (the engine's authoritative most-recent save, qa/lib_beat_driver.sh) — and if non-empty, switch the retry $msg to the EXISTING-CAMPAIGN directive (mirror the HERO_CAMP branch text, play.sh:360-373): campaign_id=<id> ALREADY EXISTS — call get_state(<id>) FIRST, DO NOT call start_world (it would mint a NEW campaign and ORPHAN this save); seat the PC if absent, then open the scene. Byte-unchanged on a fresh first attempt (no prior campaign) and on continuing beats.
The engine seam already exists: start_world(world_id, resume=<id>) at servers/engine/server.py:493-512 returns resumed:True + the same campaign; load_canon_character is idempotent on an already-seated party.
Tests (deterministic, no LLM)
- play.sh: extract the retry's campaign-selection into a sourceable shell fn; seed a STATE_DIR with one engine-minted seated campaign, assert a
first=1 RETRY selects the EXISTING campaign + emits a directive containing campaign_id=<existing> + DO NOT call start_world, NOT the start_world("<world>") cold-open text. RED today.
- engine contract (
servers/engine/tests/test_dm_session_remint.py): start_world(world)→mint A→seat PC = ONE dir; start_world(world, resume=A) returns resumed:True, campaign==A, NO second dir; start_world(world) with no resume DOES mint a second dir (documents the orphaning to avoid).
Verified plan in the workflow output (session scratchpad references it). Load-bearing beat-driver change — TDD it.
Root cause (adversarially verified, RRI 2026-06-09)
The shared engine-root of two critical bugs — the hero-bind party-wipe (vm2-opuslean-narr2) and the input-lock/no-spinner (vm2-newbie). When a cold-open beat times out (
timeout(1)rc=124, >WORLDOS_COLDOPEN_TIMEOUT400s),play.sh's one-shot retry re-mints only the--session-idand re-invokes the identical cold-open prompt — which again instructsstart_world("<world>"). The engine has no resume context, so it mints a SECOND, fresh campaign (party-less —load_canon_characternever ran), orphaning attempt-1's seated run. The viewer then auto-follows the newer empty orphan → "wiped" party + a frozen empty screen.PR #717 mitigates the read-side (the viewer demotes party-less orphans), and #718 adds the cold-open spinner — but the orphan is still created (a wasted cold-open + a data-loss footgun). This issue is the engine-side prevention.
Fix (additive, invariant-safe; engine stays sole writer)
In
scripts/play.shdm_turn()'s retry block (~287-296): before re-invoking_dm_invokeon a cold-open (first=1) turn with noHERO_CAMP, detect the campaign attempt-1 already minted —clawdnd_live_campaign_id "$ROOT" "$STATE_DIR" "$WORLD"(the engine's authoritative most-recent save,qa/lib_beat_driver.sh) — and if non-empty, switch the retry$msgto the EXISTING-CAMPAIGN directive (mirror theHERO_CAMPbranch text,play.sh:360-373):campaign_id=<id> ALREADY EXISTS — call get_state(<id>) FIRST, DO NOT call start_world (it would mint a NEW campaign and ORPHAN this save); seat the PC if absent, then open the scene.Byte-unchanged on a fresh first attempt (no prior campaign) and on continuing beats.The engine seam already exists:
start_world(world_id, resume=<id>)atservers/engine/server.py:493-512returnsresumed:True+ the same campaign;load_canon_characteris idempotent on an already-seated party.Tests (deterministic, no LLM)
first=1RETRY selects the EXISTING campaign + emits a directive containingcampaign_id=<existing>+DO NOT call start_world, NOT thestart_world("<world>")cold-open text. RED today.servers/engine/tests/test_dm_session_remint.py):start_world(world)→mint A→seat PC = ONE dir;start_world(world, resume=A)returnsresumed:True, campaign==A, NO second dir;start_world(world)with no resume DOES mint a second dir (documents the orphaning to avoid).Verified plan in the workflow output (session scratchpad references it). Load-bearing beat-driver change — TDD it.