- AppState gains play: Option<Arc<PlayTable>>, populated exactly when
controllers is (both come from the one open_controller call). Cut 8b's
own map bullet asked for a play field plus a mutex; the "one running turn
per world" mutex (PA.f68/PA.f112) instead lives inside PlayTable itself
as run_lock (landed in the prior play.rs commit), since this process
holds exactly one Arc<PlayTable> for the one world it owns and a second
AppState-level lock over the same table would be a second owner of the
same invariant. Reported per the brief; see the Hands report for the
full account.
- open_controller now reads GHOSTLIGHT_PLAY_MODEL beside its four
siblings and passes it into the same open_inference call, returning the
opened inference organ and models alongside the ControllerRunner
(OpenedControllers) so the play table shares one connection to the
transports rather than opening a second. A new open_play helper builds
the PersonaLane and PlayTable from that shared organ, using
controller_permits as the play table's own concurrency pool.
- execute_world routes "world.play" ahead of the CommandBody match (it is
not one kernel command): spawns PlayTable::run in a background task and
returns {"kind":"accepted"} immediately, exactly as the map calls for.
A missing play table is denied, not a panic.
- eve.rs's operation_schema carries "world.play" =>
"ghostlight.world_play.v0" beside "world.speak", unchanged otherwise;
world.speak and the story card are untouched (Cut 9's own deletion).
Tests: world_play_reaches_the_play_table_and_is_accepted and
world_play_without_a_play_table_is_denied (runtime.rs), plus
world_play_is_advertised_and_payload_cannot_claim_authority (eve.rs,
beside payload_cannot_claim_authority for world.speak).
Dungeon bin: 125 -> 128 tests. ghostlight lib: 622 (unchanged).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>