diff --git a/docs/releases/v2.12.0.md b/docs/releases/v2.12.0.md
index 15888867a8ec145a8dc8b2bbd2ad52072942bb8c..0d29b901d4b58428051f2027de1140661c99564e 100644
--- a/docs/releases/v2.12.0.md
+++ b/docs/releases/v2.12.0.md
@@ -5,6 +5,10 @@
 - enable rat connectivity
 - added visual indicators for selected subject and dataset in connectivity browser
 
+## Bugfix
+
+- fixed fsaverage viewer "rubber banding"
+
 ## Behind the scene
 
 - update spotlight mechanics from in-house to angular CDK
diff --git a/src/atlasComponents/sapi/sapi.service.ts b/src/atlasComponents/sapi/sapi.service.ts
index 7afa3871728516ca4ebd63c10e08b6cf88526ab7..e1648fcc7c99cc8b263ef532fc9ea01e7363fb72 100644
--- a/src/atlasComponents/sapi/sapi.service.ts
+++ b/src/atlasComponents/sapi/sapi.service.ts
@@ -21,7 +21,7 @@ export const useViewer = {
 } as const
 
 export const SIIBRA_API_VERSION_HEADER_KEY='x-siibra-api-version'
-export const EXPECTED_SIIBRA_API_VERSION = '0.3.5'
+export const EXPECTED_SIIBRA_API_VERSION = '0.3.8'
 
 let BS_ENDPOINT_CACHED_VALUE: Observable<string> = null
 
diff --git a/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.component.ts b/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.component.ts
index e4e729b1cfcf6817d00afd57681e2bbc9c817e9c..f2a869a8ab434f68802b8b34540471f2621fd46c 100644
--- a/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.component.ts
+++ b/src/viewerModule/threeSurfer/threeSurferGlue/threeSurfer.component.ts
@@ -1,8 +1,8 @@
 import { Component, Output, EventEmitter, ElementRef, OnDestroy, AfterViewInit, Inject, Optional, ChangeDetectionStrategy } from "@angular/core";
 import { EnumViewerEvt, IViewer, TViewerEvent } from "src/viewerModule/viewer.interface";
 import { combineLatest, concat, forkJoin, from, merge, NEVER, Observable, of, Subject } from "rxjs";
-import { catchError, debounceTime, distinctUntilChanged, filter, map, scan, shareReplay, switchMap, withLatestFrom } from "rxjs/operators";
-import { ComponentStore } from "src/viewerModule/componentStore";
+import { catchError, debounceTime, distinctUntilChanged, filter, map, scan, shareReplay, startWith, switchMap, tap, withLatestFrom } from "rxjs/operators";
+import { ComponentStore, LockError } from "src/viewerModule/componentStore";
 import { select, Store } from "@ngrx/store";
 import { ClickInterceptor, CLICK_INTERCEPTOR_INJECTOR } from "src/util";
 import { MatSnackBar } from "@angular/material/snack-bar";
@@ -27,7 +27,7 @@ type TInternalState = {
   mode: string
   hemisphere: 'left' | 'right' | 'both'
 }
-const pZoomFactor = 5e3
+const pZoomFactor = 7e3
 
 type THandlingCustomEv = {
   regions: SxplrRegion[]
@@ -107,11 +107,15 @@ type LateralityRecord<T> = Record<string, T>
 const threshold = 1e-3
 
 function cameraNavsAreSimilar(c1: TCameraOrientation, c2: TCameraOrientation){
+
+  // if same reference, return true
   if (c1 === c2) return true
-  if (!!c1 && !!c2) return true
 
-  if (!c1 && !!c2) return false
-  if (!c2 && !!c1) return false
+  // if both falsy, return true
+  if (!c1 && !c2) return true
+
+  if (!c1 && c2) return false
+  if (!c2 && c1) return false
 
   if (Math.abs(c1.perspectiveZoom - c2.perspectiveZoom) > threshold) return false
   if ([0, 1, 2, 3].some(
@@ -141,9 +145,57 @@ export class ThreeSurferGlueCmp implements IViewer<'threeSurfer'>, AfterViewInit
   viewerEvent = new EventEmitter<TViewerEvent<'threeSurfer'>>()
 
   private domEl: HTMLElement
-  private mainStoreCameraNav: TCameraOrientation = null
-  private localCameraNav: TCameraOrientation = null
 
+  #storeNavigation = this.store$.pipe(
+    select(atlasSelection.selectors.navigation)
+  )
+
+  #componentStoreNavigation = this.navStateStoreRelay.select(s => s)
+  
+  #internalNavigation = this.#cameraEv$.pipe(
+    filter(v => !!v && !!(this.tsRef?.camera?.matrix)),
+    map(() => {
+      const { tsRef } = this
+      return {
+        _po: null,
+        _pz: null,
+        _calculate(){
+          if (!tsRef) return
+          const THREE = (window as any).ThreeSurfer.THREE
+          
+          const q = new THREE.Quaternion()
+          const t = new THREE.Vector3()
+          const s = new THREE.Vector3()
+
+          /**
+           * ThreeJS interpretes the scene differently to neuroglancer in subtle ways. 
+           * At [0, 0, 0, 1] decomposed camera quaternion, for example,
+           * - ThreeJS: view from superior -> inferior, anterior as top, right hemisphere as right
+           * - NG: view from from inferior -> superior, posterior as top, left hemisphere as right
+           * 
+           * multiplying the exchange factor [-1, 0, 0, 0] converts ThreeJS convention to NG convention
+           */
+          const cameraM = tsRef.camera.matrix
+          cameraM.decompose(t, q, s)
+          const exchangeFactor = new THREE.Quaternion(-1, 0, 0, 0)
+          this._po = q.multiply(exchangeFactor).toArray()
+          this._pz = t.length() * pZoomFactor // use zoom as used in main store
+        },
+        get perspectiveOrientation(){
+          if (!this._po) {
+            this._calculate()
+          }
+          return this._po
+        },
+        get perspectiveZoom() {
+          if (!this._pz) {
+            this._calculate()
+          }
+          return this._pz
+        }
+      } as TCameraOrientation
+    })
+  )
 
   private internalStateNext: (arg: TInteralStatePayload<TInternalState>) => void
 
@@ -336,9 +388,9 @@ export class ThreeSurferGlueCmp implements IViewer<'threeSurfer'>, AfterViewInit
 
   constructor(
     private effect: ThreeSurferEffects,
-    private el: ElementRef,
+    el: ElementRef,
     private store$: Store,
-    private navStateStoreRelay: ComponentStore<{ perspectiveOrientation: [number, number, number, number], perspectiveZoom: number }>,
+    private navStateStoreRelay: ComponentStore<TCameraOrientation>,
     private sapi: SAPI,
     private snackbar: MatSnackBar,
     @Optional() intViewerStateSvc: ViewerInternalStateSvc,
@@ -379,7 +431,7 @@ export class ThreeSurferGlueCmp implements IViewer<'threeSurfer'>, AfterViewInit
       const handleClick = (ev: MouseEvent) => {
 
         // if does not click inside container, ignore
-        if (!(this.el.nativeElement as HTMLElement).contains(ev.target as HTMLElement)) {
+        if (!(el.nativeElement as HTMLElement).contains(ev.target as HTMLElement)) {
           return true
         }
         
@@ -404,88 +456,87 @@ export class ThreeSurferGlueCmp implements IViewer<'threeSurfer'>, AfterViewInit
       )
     }
     
-    this.domEl = this.el.nativeElement
+    this.domEl = el.nativeElement
 
     /**
      * subscribe to camera custom event
      */
-    const cameraSub = this.#cameraEv$.pipe(
-      filter(v => !!v),
-      debounceTime(160)
-    ).subscribe(() => {
-      
-      const THREE = (window as any).ThreeSurfer.THREE
-      
-      const q = new THREE.Quaternion()
-      const t = new THREE.Vector3()
-      const s = new THREE.Vector3()
-
-      /**
-       * ThreeJS interpretes the scene differently to neuroglancer in subtle ways. 
-       * At [0, 0, 0, 1] decomposed camera quaternion, for example,
-       * - ThreeJS: view from superior -> inferior, anterior as top, right hemisphere as right
-       * - NG: view from from inferior -> superior, posterior as top, left hemisphere as right
-       * 
-       * multiplying the exchange factor [-1, 0, 0, 0] converts ThreeJS convention to NG convention
-       */
-      const cameraM = this.tsRef.camera.matrix
-      cameraM.decompose(t, q, s)
-      const exchangeFactor = new THREE.Quaternion(-1, 0, 0, 0)
-
+    const setReconcilState = merge(
+      this.#internalNavigation.pipe(
+        filter(v => !!v),
+        tap(() => {
+          try {
+            this.releaseRelayLock = this.navStateStoreRelay.getLock()
+          } catch (e) {
+            if (!(e instanceof LockError)) {
+              throw e
+            }
+          }
+        }),
+        debounceTime(160),
+        tap(() => {
+          if (this.releaseRelayLock) {
+            this.releaseRelayLock()
+            this.releaseRelayLock = null
+          } else {
+            console.warn(`this.releaseRelayLock not aquired, component may not function properly`)
+          }  
+        })
+      ),
+      this.#storeNavigation,
+    ).pipe(
+      filter(v => !!v)
+    ).subscribe(nav => {
       try {
         this.navStateStoreRelay.setState({
-          perspectiveOrientation: q.multiply(exchangeFactor).toArray(),
-          perspectiveZoom: t.length()
+          perspectiveOrientation: nav.perspectiveOrientation,
+          perspectiveZoom: nav.perspectiveZoom
         })
-      } catch (_e) {
-        // LockError, ignore
+      } catch (e) {
+        if (!(e instanceof LockError)) {
+          throw e
+        }
       }
     })
 
     this.onDestroyCb.push(
-      () => cameraSub.unsubscribe()
+      () => setReconcilState.unsubscribe()
     )
 
     /**
      * subscribe to navstore relay store and negotiate setting global state
      */
-    const navStateSub = this.navStateStoreRelay.select(s => s).subscribe(v => {
-      this.store$.dispatch(
-        atlasSelection.actions.setNavigation({
+    const reconciliatorSub = combineLatest([
+      this.#storeNavigation.pipe(
+        startWith(null as TCameraOrientation)
+      ),
+      this.#componentStoreNavigation.pipe(
+        startWith(null as TCameraOrientation),
+      ),
+      this.#internalNavigation.pipe(
+        startWith(null as TCameraOrientation),
+      )
+    ]).pipe(
+      debounceTime(160),
+      filter(() => !this.navStateStoreRelay.isLocked)
+    ).subscribe(([ storeNav, reconcilNav, internalNav ]) => {
+      if (!cameraNavsAreSimilar(storeNav, reconcilNav) && reconcilNav) {
+        this.store$.dispatch(atlasSelection.actions.setNavigation({
           navigation: {
             position: [0, 0, 0],
             orientation: [0, 0, 0, 1],
             zoom: 1e6,
-            perspectiveOrientation: v.perspectiveOrientation,
-            perspectiveZoom: v.perspectiveZoom * pZoomFactor
+            perspectiveOrientation: reconcilNav.perspectiveOrientation,
+            perspectiveZoom: reconcilNav.perspectiveZoom
           }
-        })
-      )
-    })
-
-    this.onDestroyCb.push(
-      () => navStateSub.unsubscribe()
-    )
-
-    /**
-     * subscribe to main store and negotiate with relay to set camera
-     */
-    const navSub = this.store$.pipe(
-      select(atlasSelection.selectors.navigation),
-      filter(v => !!v),
-    ).subscribe(nav => {
-      const { perspectiveOrientation, perspectiveZoom } = nav
-      this.mainStoreCameraNav = {
-        perspectiveOrientation,
-        perspectiveZoom
+        }))
       }
 
-      if (!cameraNavsAreSimilar(this.mainStoreCameraNav, this.localCameraNav)) {
-        this.relayStoreLock = this.navStateStoreRelay.getLock()
+      if (!cameraNavsAreSimilar(reconcilNav, internalNav) && reconcilNav) {
         const THREE = (window as any).ThreeSurfer.THREE
         
-        const cameraQuat = new THREE.Quaternion(...this.mainStoreCameraNav.perspectiveOrientation)
-        const cameraPos = new THREE.Vector3(0, 0, this.mainStoreCameraNav.perspectiveZoom / pZoomFactor)
+        const cameraQuat = new THREE.Quaternion(...reconcilNav.perspectiveOrientation)
+        const cameraPos = new THREE.Vector3(0, 0, reconcilNav.perspectiveZoom / pZoomFactor)
         
         /**
          * ThreeJS interpretes the scene differently to neuroglancer in subtle ways. 
@@ -501,19 +552,18 @@ export class ThreeSurferGlueCmp implements IViewer<'threeSurfer'>, AfterViewInit
         cameraPos.applyQuaternion(cameraQuat)
         this.toTsRef(tsRef => {
           tsRef.camera.position.copy(cameraPos)
-          if (this.relayStoreLock) this.relayStoreLock()
         })
       }
     })
 
     this.onDestroyCb.push(
-      () => navSub.unsubscribe()
+      () => reconciliatorSub.unsubscribe()
     )
   }
 
   private tsRef: TThreeSurfer
 
-  private relayStoreLock: () => void = null
+  private releaseRelayLock: () => void = null
   private tsRefInitCb: ((tsRef: any) => void)[] = []
   private toTsRef(callback: (tsRef: any) => void) {
     if (this.tsRef) {