@@ -126,7 +126,7 @@ void clearedViewerOwnershipMakesAnUnchangedRegionStaleForReconnect() {
126126 }
127127
128128 @ Test
129- void sameImmutableInteractionSnapshotReusesWithoutRepeatedIdentityReads () {
129+ void unchangedRegionReusesWithoutRepeatedSessionOrIdentityReads () {
130130 TableSessionContext session = mock (TableSessionContext .class );
131131 SparrowRayInteractionProxyCoordinator .Backend backend = mock (
132132 SparrowRayInteractionProxyCoordinator .Backend .class
@@ -162,11 +162,12 @@ void sameImmutableInteractionSnapshotReusesWithoutRepeatedIdentityReads() {
162162 verify (backend , times (1 )).spawn (viewer , List .of (proxy ));
163163 verify (proxy , times (1 )).entityId ();
164164 verify (session , times (1 )).id ();
165+ verify (session , times (1 )).onlinePlayer (VIEWER_ID );
165166 assertEquals (1 , coordinator .entityCount ());
166167 }
167168
168169 @ Test
169- void mutableInteractionListGeometryChangeCannotHitIdentityFastPath () {
170+ void mutableInteractionListGeometryChangeForcesRebuild () {
170171 TableSessionContext session = mock (TableSessionContext .class );
171172 SparrowRayInteractionProxyCoordinator .Backend backend = mock (
172173 SparrowRayInteractionProxyCoordinator .Backend .class
@@ -256,6 +257,55 @@ void multipleProxyOwnershipRemainsCompleteWithCachedFirstEntityId() {
256257 verify (secondProxy , times (3 )).entityId ();
257258 }
258259
260+ @ Test
261+ void offlineCachedViewerUsesFreshSessionPlayer () {
262+ TableSessionContext session = mock (TableSessionContext .class );
263+ SparrowRayInteractionProxyCoordinator .Backend backend = mock (
264+ SparrowRayInteractionProxyCoordinator .Backend .class
265+ );
266+ SparrowRayInteractionProxyCoordinator .ClientProxy firstProxy = mock (
267+ SparrowRayInteractionProxyCoordinator .ClientProxy .class
268+ );
269+ SparrowRayInteractionProxyCoordinator .ClientProxy reconnectedProxy = mock (
270+ SparrowRayInteractionProxyCoordinator .ClientProxy .class
271+ );
272+ Player firstViewer = mock (Player .class );
273+ Player reconnectedViewer = mock (Player .class );
274+ when (session .id ()).thenReturn ("table-a" );
275+ when (session .onlinePlayer (VIEWER_ID )).thenReturn (firstViewer ).thenReturn (reconnectedViewer );
276+ when (firstViewer .isOnline ()).thenReturn (true );
277+ when (reconnectedViewer .isOnline ()).thenReturn (true );
278+ when (backend .available ()).thenReturn (true );
279+ when (backend .create (eq (firstViewer ), any ())).thenReturn (List .of (firstProxy ));
280+ when (backend .create (eq (reconnectedViewer ), any ())).thenReturn (List .of (reconnectedProxy ));
281+ when (firstProxy .entityId ()).thenReturn (1211 );
282+ when (reconnectedProxy .entityId ()).thenReturn (1212 );
283+ doAnswer (invocation -> {
284+ invocation .<Runnable >getArgument (1 ).run ();
285+ return null ;
286+ }).when (session ).runForViewer (any (Player .class ), any (Runnable .class ));
287+ SparrowRayInteractionProxyCoordinator coordinator = new SparrowRayInteractionProxyCoordinator (
288+ session ,
289+ backend
290+ );
291+ Map <UUID , List <DisplayInteractionRayRegistry .RayInteraction >> interactions = Map .of (
292+ VIEWER_ID ,
293+ List .of (interaction ())
294+ );
295+
296+ coordinator .replace ("actions" , interactions );
297+ when (firstViewer .isOnline ()).thenReturn (false );
298+ coordinator .replace ("actions" , interactions );
299+
300+ verify (backend ).spawn (firstViewer , List .of (firstProxy ));
301+ verify (backend ).spawn (reconnectedViewer , List .of (reconnectedProxy ));
302+ verify (backend , never ()).destroy (firstViewer , List .of (firstProxy ));
303+ verify (session , times (2 )).onlinePlayer (VIEWER_ID );
304+ assertNull (ClientInteractionProxyRegistry .tableIdFor (1211 , VIEWER_ID ));
305+ assertEquals ("table-a" , ClientInteractionProxyRegistry .tableIdFor (1212 , VIEWER_ID ));
306+ assertEquals (1 , coordinator .entityCount ());
307+ }
308+
259309 @ Test
260310 void cachedEntityCountTracksTheSameViewerAcrossMultipleRegions () {
261311 TableSessionContext session = mock (TableSessionContext .class );
0 commit comments